1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
|
import os
from abc import ABCMeta, abstractmethod
from prometheus_client import CollectorRegistry
from prometheus_client import start_http_server as pc_start_http_server
from prometheus_client.multiprocess import MultiProcessCollector
from prometheus_client.multiprocess import mark_process_dead as pc_mark_process_dead
from . import PrometheusMetrics
def _check_multiproc_env_var():
"""
Checks that the `PROMETHEUS_MULTIPROC_DIR` environment variable is set,
which is required for the multiprocess collector to work properly.
:raises ValueError: if the environment variable is not set
or if it does not point to a directory
"""
if 'PROMETHEUS_MULTIPROC_DIR' in os.environ:
if os.path.isdir(os.environ['PROMETHEUS_MULTIPROC_DIR']):
return
elif 'prometheus_multiproc_dir' in os.environ:
if os.path.isdir(os.environ['prometheus_multiproc_dir']):
return
raise ValueError('one of env PROMETHEUS_MULTIPROC_DIR or env prometheus_multiproc_dir ' +
'must be set and be a directory')
class MultiprocessPrometheusMetrics(PrometheusMetrics):
"""
An extension of the `PrometheusMetrics` class that provides
convenience functions for multiprocess applications.
There are ready to use classes for uWSGI and Gunicorn.
For everything else, extend this class and override
the `should_start_http_server` to only return `True`
from one process only - typically the main one.
Note: you will need to explicitly call the `start_http_server` function.
"""
__metaclass__ = ABCMeta
def __init__(self, app=None, **kwargs):
"""
Create a new multiprocess-aware Prometheus metrics export configuration.
:param registry: the Prometheus Registry to use (can be `None` and it
will be registered with `prometheus_client.multiprocess.MultiProcessCollector`)
"""
_check_multiproc_env_var()
registry = kwargs.pop('registry', CollectorRegistry())
MultiProcessCollector(registry)
kwargs.pop('path', None) # remove the path parameter if it was passed in
super().__init__(
app=app, path=None, registry=registry, **kwargs
)
def start_http_server(self, port, host='0.0.0.0', endpoint=None, ssl=None):
"""
Start an HTTP server for exposing the metrics, if the
`should_start_http_server` function says we should, otherwise just return.
Uses the implementation from `prometheus_client` rather than a Flask app.
:param port: the HTTP port to expose the metrics endpoint on
:param host: the HTTP host to listen on (default: `0.0.0.0`)
:param endpoint: **ignored**, the HTTP server will respond on any path
:param ssl: **ignored**, the server will not support SSL/HTTPS
"""
if self.should_start_http_server():
pc_start_http_server(port, host, registry=self.registry)
@abstractmethod
def should_start_http_server(self):
"""
Whether or not to start the HTTP server.
Only return `True` from one process only, typically the main one.
Note: you still need to explicitly call the `start_http_server` function.
:return: `True` if the server should start, `False` otherwise
"""
pass
class MultiprocessInternalPrometheusMetrics(MultiprocessPrometheusMetrics):
"""
A multiprocess `PrometheusMetrics` extension with the metrics endpoint
registered on the Flask app internally.
This variant is expected to expose the metrics endpoint on the same server
as the production endpoints are served too.
Alternatively, you can use the instance functions as well.
"""
def __init__(self, app=None, path='/metrics', **kwargs):
"""
Create a new multiprocess-aware Prometheus metrics export configuration.
"""
super().__init__(app=app, **kwargs)
if app:
self.register_endpoint(path)
else:
self.path = path
def should_start_http_server(self):
return False
@classmethod
def start_http_server_when_ready(cls, port, host='0.0.0.0'):
import warnings
warnings.warn(
'The `MultiprocessInternalPrometheusMetrics` class is expected to expose the metrics endpoint '
'on the same Flask application, so the `start_http_server_when_ready` should not be called.',
UserWarning
)
class UWsgiPrometheusMetrics(MultiprocessPrometheusMetrics):
"""
A multiprocess `PrometheusMetrics` extension targeting uWSGI deployments.
This will only start the HTTP server for metrics on the main process,
indicated by `uwsgi.masterpid()`.
"""
def should_start_http_server(self):
import uwsgi
return os.getpid() == uwsgi.masterpid()
class GunicornPrometheusMetrics(MultiprocessPrometheusMetrics):
"""
A multiprocess `PrometheusMetrics` extension targeting Gunicorn deployments.
This variant is expected to serve the metrics endpoint on an individual HTTP server.
See `GunicornInternalPrometheusMetrics` for one that serves the metrics endpoint
on the same server as the other endpoints.
It should have Gunicorn configuration to start the HTTP server like this:
from prometheus_flask_exporter.multiprocess import GunicornPrometheusMetrics
def when_ready(server):
GunicornPrometheusMetrics.start_http_server_when_ready(metrics_port)
def child_exit(server, worker):
GunicornPrometheusMetrics.mark_process_dead_on_child_exit(worker.pid)
Alternatively, you can use the instance functions as well.
"""
def should_start_http_server(self):
return True
@classmethod
def start_http_server_when_ready(cls, port, host='0.0.0.0'):
"""
Start the HTTP server from the Gunicorn config module.
Doesn't necessarily need an instance, a class is fine.
Example:
def when_ready(server):
GunicornPrometheusMetrics.start_http_server_when_ready(metrics_port)
:param port: the HTTP port to expose the metrics endpoint on
:param host: the HTTP host to listen on (default: `0.0.0.0`)
"""
_check_multiproc_env_var()
GunicornPrometheusMetrics().start_http_server(port, host)
@classmethod
def mark_process_dead_on_child_exit(cls, pid):
"""
Mark a child worker as exited from the Gunicorn config module.
Example:
def child_exit(server, worker):
GunicornPrometheusMetrics.mark_process_dead_on_child_exit(worker.pid)
:param pid: the worker pid that has exited
"""
pc_mark_process_dead(pid)
class GunicornInternalPrometheusMetrics(GunicornPrometheusMetrics):
"""
A multiprocess `PrometheusMetrics` extension targeting Gunicorn deployments.
This variant is expected to expose the metrics endpoint on the same server
as the production endpoints are served too.
See also the `GunicornPrometheusMetrics` class that will start a
new HTTP server for the metrics endpoint.
It should have Gunicorn configuration to start the HTTP server like this:
from prometheus_flask_exporter.multiprocess import GunicornInternalPrometheusMetrics
def child_exit(server, worker):
GunicornInternalPrometheusMetrics.mark_process_dead_on_child_exit(worker.pid)
Alternatively, you can use the instance functions as well.
"""
def __init__(self, app=None, path='/metrics', **kwargs):
"""
Create a new multiprocess-aware Prometheus metrics export configuration.
"""
super().__init__(app=app, **kwargs)
if app:
self.register_endpoint(path)
else:
self.path = path
def should_start_http_server(self):
return False
@classmethod
def start_http_server_when_ready(cls, port, host='0.0.0.0'):
import warnings
warnings.warn(
'The `GunicornInternalPrometheusMetrics` class is expected to expose the metrics endpoint '
'on the same Flask application, so the `start_http_server_when_ready` should not be called. '
'Maybe you are looking for the `GunicornPrometheusMetrics` class?',
UserWarning
)
|