diff --git a/devel/examples/prom-multi-proc-gunicorn/README.md b/devel/examples/prom-multi-proc-gunicorn/README.md new file mode 100644 index 0000000..68b406f --- /dev/null +++ b/devel/examples/prom-multi-proc-gunicorn/README.md @@ -0,0 +1,61 @@ +# Example `prom-multi-proc-gunicorn` + +Minimal example that shows integration of FastAPI (Gunicorn) with the Prometheus +client library in multi process mode without Prometheus FastAPI Instrumentator. +Highlights missing metrics that are not supported in multi process mode. + +To run the example, you must have run `poetry install` and `poetry shell` in the +root of this repository. The following commands are executed relative to this +directory. + +Set environment variable to an unused location: + +```shell +export PROMETHEUS_MULTIPROC_DIR=/tmp/python-testing-pfi/560223ba-887f-429a-9c48-933df56a68ba +``` + +Start the app with Gunicorn using two Uvicorn workers: + +```shell +rm -rf "$PROMETHEUS_MULTIPROC_DIR" +mkdir -p "$PROMETHEUS_MULTIPROC_DIR" +gunicorn main:app \ + --config gunicorn.conf.py \ + --workers 2 \ + --worker-class uvicorn.workers.UvicornWorker \ + --bind 0.0.0.0:8080 +``` + +Interact with app: + +```shell +for i in {1..5}; do curl localhost:8080/ping; done +curl localhost:8080/metrics +``` + +You should see something like this: + +```txt +# TYPE ping_total counter +ping_total 5.0 +# HELP metrics_total Number of metrics calls. +# TYPE metrics_total counter +metrics_total 1.0 +# HELP main_total Counts of main executions. +# TYPE main_total counter +main_total 2.0 +``` + +Check the returned metrics: + +- `main_total` is `2`, because Gunicorn is using two workers. +- There are no `created_by` metrics. These are not supported by the Prometheus + client library in multi process mode. +- No metrics for things like CPU and memory. They come from components like the + `ProcessCollector` and `PlatformCollector` which are not supported by the + Prometheus client library in multi process mode. + +Links: + +- +- diff --git a/devel/examples/prom-multi-proc-gunicorn/gunicorn.conf.py b/devel/examples/prom-multi-proc-gunicorn/gunicorn.conf.py new file mode 100644 index 0000000..aa3e7ae --- /dev/null +++ b/devel/examples/prom-multi-proc-gunicorn/gunicorn.conf.py @@ -0,0 +1,5 @@ +from prometheus_client import multiprocess + + +def child_exit(server, worker): + multiprocess.mark_process_dead(worker.pid) diff --git a/devel/examples/prom-multi-proc-gunicorn/main.py b/devel/examples/prom-multi-proc-gunicorn/main.py new file mode 100644 index 0000000..b4db1cd --- /dev/null +++ b/devel/examples/prom-multi-proc-gunicorn/main.py @@ -0,0 +1,46 @@ +import os + +from fastapi import FastAPI +from prometheus_client import ( + CONTENT_TYPE_LATEST, + CollectorRegistry, + Counter, + generate_latest, + multiprocess, +) +from starlette.responses import Response + +if "PROMETHEUS_MULTIPROC_DIR" not in os.environ: + raise ValueError("PROMETHEUS_MULTIPROC_DIR must be set to existing empty dir.") + + +PING_TOTAL = Counter("ping", "Number of pings calls.") +METRICS_TOTAL = Counter("metrics", "Number of metrics calls.") + +MAIN_TOTAL = Counter("main", "Counts of main executions.") +MAIN_TOTAL.inc() + + +app = FastAPI() + + +@app.get("/ping") +def get_ping(): + PING_TOTAL.inc() + return "pong" + + +@app.get("/metrics") +def get_metrics(): + METRICS_TOTAL.inc() + + # Note the ephemeral registry being used here. This follows the Prometheus + # client library documentation. It comes with multiple caveats. Using a + # persistent registry might work on first glance but it will lead to issues. + # For a long time PFI used a persistent registry, which was wrong. + registry = CollectorRegistry() + multiprocess.MultiProcessCollector(registry) + resp = Response(content=generate_latest(registry)) + + resp.headers["Content-Type"] = CONTENT_TYPE_LATEST + return resp