Repository: prometheus/client_python
Pull request #999 (2024-01-25 09:24:07)

Enable graceful shutdown for start_{http,wsgi}_server
Hello @csmarchbanks, I made the `start_http_server` function a bit easier to cancel gracefully.

arsatiki: It's quite useful with long(ish) teardown situations and short scrape intervals. The metrics can get pretty skewed and meaningless since there's no new data coming in. Easier just to not respond at all instead of inventing new `shuttingdown` metrics or such.

Patch:
diff --git a/docs/content/exporting/http/_index.md b/docs/content/exporting/http/_index.md
index b074a3b..71edc7e 100644
--- a/docs/content/exporting/http/_index.md
+++ b/docs/content/exporting/http/_index.md
@@ -18,6 +18,15 @@ start_http_server(8000)
 
 Visit [http://localhost:8000/](http://localhost:8000/) to view the metrics.
 
+The function will return the HTTP server and thread objects, which can be used
+to shutdown the server gracefully:
+
+```python
+server, t = start_http_server(8000)
+server.shutdown()
+t.join()
+```
+
 To add Prometheus exposition to an existing HTTP server, see the `MetricsHandler` class
 which provides a `BaseHTTPRequestHandler`. It also serves as a simple example of how
 to write a custom endpoint.
diff --git a/docs/content/multiprocess/_index.md b/docs/content/multiprocess/_index.md
index c69cad8..bf57d35 100644
--- a/docs/content/multiprocess/_index.md
+++ b/docs/content/multiprocess/_index.md
@@ -18,6 +18,7 @@ This comes with a number of limitations:
 - The pushgateway cannot be used
 - Gauges cannot use the `pid` label
 - Exemplars are not supported
+- Remove and Clear of labels are currently not supported in multiprocess mode.
 
 There's several steps to getting this working:
 
diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py
index f2b7442..30194dd 100644
--- a/prometheus_client/exposition.py
+++ b/prometheus_client/exposition.py
@@ -210,7 +210,7 @@ def start_wsgi_server(
         client_capath: Optional[str] = None,
         protocol: int = ssl.PROTOCOL_TLS_SERVER,
         client_auth_required: bool = False,
-) -> None:
+) -> Tuple[WSGIServer, threading.Thread]:
     """Starts a WSGI server for prometheus metrics as a daemon thread."""
 
     class TmpServer(ThreadingWSGIServer):
@@ -226,6 +226,8 @@ def start_wsgi_server(
     t.daemon = True
     t.start()
 
+    return httpd, t
+
 
 start_http_server = start_wsgi_server
 
diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py
index 7e5b030..34305a1 100644
--- a/prometheus_client/metrics.py
+++ b/prometheus_client/metrics.py
@@ -6,6 +6,7 @@ from typing import (
     Any, Callable, Dict, Iterable, List, Literal, Optional, Sequence, Tuple,
     Type, TypeVar, Union,
 )
+import warnings
 
 from . import values  # retain this import style for testability
 from .context_managers import ExceptionCounter, InprogressTracker, Timer
@@ -210,6 +211,11 @@ class MetricWrapperBase(Collector):
             return self._metrics[labelvalues]
 
     def remove(self, *labelvalues: Any) -> None:
+        if 'prometheus_multiproc_dir' in os.environ or 'PROMETHEUS_MULTIPROC_DIR' in os.environ:
+            warnings.warn(
+                "Removal of labels has not been implemented in  multi-process mode yet.",
+                UserWarning)
+
         if not self._labelnames:
             raise ValueError('No label names were set when constructing %s' % self)
 
@@ -222,6 +228,10 @@ class MetricWrapperBase(Collector):
 
     def clear(self) -> None:
         """Remove all labelsets from the metric"""
+        if 'prometheus_multiproc_dir' in os.environ or 'PROMETHEUS_MULTIPROC_DIR' in os.environ:
+            warnings.warn(
+                "Clearing labels has not been implemented in multi-process mode yet",
+                UserWarning)
         with self._lock:
             self._metrics = {}
 
diff --git a/prometheus_client/openmetrics/exposition.py b/prometheus_client/openmetrics/exposition.py
index b019030..26f3109 100644
--- a/prometheus_client/openmetrics/exposition.py
+++ b/prometheus_client/openmetrics/exposition.py
@@ -3,7 +3,7 @@
 
 from ..utils import floatToGoString
 
-CONTENT_TYPE_LATEST = 'application/openmetrics-text; version=0.0.1; charset=utf-8'
+CONTENT_TYPE_LATEST = 'application/openmetrics-text; version=1.0.0; charset=utf-8'
 """Content type of the latest OpenMetrics text format"""
 
 


Test patch:
diff --git a/tests/test_multiprocess.py b/tests/test_multiprocess.py
index 6e188e5..77fd3d8 100644
--- a/tests/test_multiprocess.py
+++ b/tests/test_multiprocess.py
@@ -381,6 +381,22 @@ class TestMultiProcess(unittest.TestCase):
             os.path.join(self.tempdir, 'gauge_livesum_9999999.db'),
         ]))
 
+    def test_remove_clear_warning(self):
+        os.environ['PROMETHEUS_MULTIPROC_DIR'] = self.tempdir
+        with warnings.catch_warnings(record=True) as w:
+            values.ValueClass = get_value_class()
+            registry = CollectorRegistry()
+            collector = MultiProcessCollector(registry)
+            counter = Counter('c', 'help', labelnames=['label'], registry=None)
+            counter.labels('label').inc()
+            counter.remove('label')
+            counter.clear()
+            assert os.environ['PROMETHEUS_MULTIPROC_DIR'] == self.tempdir
+            assert issubclass(w[0].category, UserWarning)
+            assert "Removal of labels has not been implemented" in str(w[0].message)
+            assert issubclass(w[-1].category, UserWarning)
+            assert "Clearing labels has not been implemented" in str(w[-1].message)
+
 
 class TestMmapedDict(unittest.TestCase):
     def setUp(self):