diff --git a/pyproject.toml b/pyproject.toml index 919c1778987..62433a3f552 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,8 @@ dependencies = [ "pathspec", "pip-tools>=6.6.2", "pip-requirements-parser>=31.2.0", - "prometheus-client>=0.10.0", + # Lowest version of prometheus_client that supports 3.10 + "prometheus-client>=0.12.0", "psutil", "pynvml<12", "python-dateutil", diff --git a/src/bentoml/_internal/server/metrics/prometheus.py b/src/bentoml/_internal/server/metrics/prometheus.py index 9583e2d87fa..b9295b0a441 100644 --- a/src/bentoml/_internal/server/metrics/prometheus.py +++ b/src/bentoml/_internal/server/metrics/prometheus.py @@ -96,9 +96,25 @@ def start_http_server(self, port: int, addr: str = "") -> None: registry=self.registry, ) + start_wsgi_server = start_http_server + + def write_to_textfile(self, path: str) -> None: + """ + Write metrics to given path. This is intended to be used with + the Node expoerter textfile collector. + + Args: + path: path to write the metrics to. This file must end + with '.prom' for the textfile collector to process it. + """ + self.prometheus_client.write_to_textfile(path, registry=self.registry) + def make_wsgi_app(self) -> ext.WSGIApp: return self.prometheus_client.make_wsgi_app(registry=self.registry) # type: ignore (unfinished prometheus types) + def make_asgi_app(self) -> ext.ASGIApp: + return self.prometheus_client.make_asgi_app(registry=self.registry) # type: ignore (unfinished prometheus types) + def generate_latest(self): if self.multiproc: registry = self.prometheus_client.CollectorRegistry() diff --git a/src/bentoml/metrics.py b/src/bentoml/metrics.py index 2c5401222bf..cbed5b723d9 100644 --- a/src/bentoml/metrics.py +++ b/src/bentoml/metrics.py @@ -20,23 +20,26 @@ # This sets of functions are implemented in the PrometheusClient class _INTERNAL_IMPL = [ "start_http_server", + "start_wsgi_server", "make_wsgi_app", + "make_asgi_app", "generate_latest", "text_string_to_metric_families", + "write_to_textfile", ] _NOT_IMPLEMENTED = [ "delete_from_gateway", "instance_ip_grouping_key", - "make_asgi_app", "push_to_gateway", "pushadd_to_gateway", - "start_wsgi_server", - "write_to_textfile", ] _NOT_SUPPORTED = [ "GC_COLLECTOR", + "GCCollector", "PLATFORM_COLLECTOR", + "PlatformCollector", "PROCESS_COLLECTOR", + "ProcessCollector", "REGISTRY", ] + _NOT_IMPLEMENTED _PROPERTY = ["CONTENT_TYPE_LATEST"] @@ -45,8 +48,18 @@ def __dir__() -> list[str]: # This is for IPython and IDE autocompletion. metrics_client = BentoMLContainer.metrics_client.get() - _dir: set[str] = set(dir(metrics_client.prometheus_client)) - set(_NOT_SUPPORTED) - return list(_dir) + return list(set(dir(metrics_client.prometheus_client)) - set(_NOT_SUPPORTED)) + + +def __getattr__(item: t.Any): + # This is the entrypoint for all bentoml.metrics.* + if item in _PROPERTY: + logger.warning( + "'%s' is a '@property', which means there is no lazy loading. See https://docs.bentoml.org/en/latest/guides/metrics.html.", + item, + ) + return getattr(_LazyMetric(item), item) + return _LazyMetric(item) class _LazyMetric: @@ -111,13 +124,3 @@ def _load_proxy( proxy = proxy(*self._args, **self._kwargs) self._initialized = True return proxy - - -def __getattr__(item: t.Any): - if item in _PROPERTY: - logger.warning( - "'%s' is a '@property', which means there is no lazy loading. See https://docs.bentoml.org/en/latest/guides/metrics.html.", - item, - ) - return getattr(_LazyMetric(item), item) - return _LazyMetric(item) diff --git a/src/bentoml/start.py b/src/bentoml/start.py index 2f3c1005017..63f72c855e2 100644 --- a/src/bentoml/start.py +++ b/src/bentoml/start.py @@ -42,7 +42,7 @@ def start_runner_server( from ._internal.utils.circus import create_standalone_arbiter from ._internal.utils.analytics import track_serve - ensure_prometheus_dir() + prometheus_dir = ensure_prometheus_dir() working_dir = os.path.realpath(os.path.expanduser(working_dir)) svc = load(bento_identifier, working_dir=working_dir, standalone_load=True) @@ -83,6 +83,8 @@ def start_runner_server( "--no-access-log", "--worker-id", "$(circus.wid)", + "--prometheus-dir", + prometheus_dir, ], copy_env=True, stop_children=True, diff --git a/src/bentoml_cli/worker/grpc_prometheus_server.py b/src/bentoml_cli/worker/grpc_prometheus_server.py index 47a1d8a4e62..ab0286272e2 100644 --- a/src/bentoml_cli/worker/grpc_prometheus_server.py +++ b/src/bentoml_cli/worker/grpc_prometheus_server.py @@ -68,10 +68,11 @@ def main(fd: int, backlog: int, prometheus_dir: str | None): configure_server_logging() BentoMLContainer.development_mode.set(False) - metrics_client = BentoMLContainer.metrics_client.get() if prometheus_dir is not None: BentoMLContainer.prometheus_multiproc_dir.set(prometheus_dir) + metrics_client = BentoMLContainer.metrics_client.get() + # create a ASGI app that wraps around the default HTTP prometheus server. prom_app = Starlette( debug=get_debug_mode(), middleware=[Middleware(GenerateLatestMiddleware)]