client_python icon indicating copy to clipboard operation
client_python copied to clipboard

Multiprocessing with Gunicorn + FastAPI

Open IWillPull opened this issue 3 years ago • 4 comments

I've been trying to make multiprocessing work with FastAPI, but it just doesn't seem to happen.

This part is what I've been struggling the most:

def app(environ, start_response):
    registry = CollectorRegistry()
    multiprocess.MultiProcessCollector(registry)
    data = generate_latest(registry)
    status = '200 OK'
    response_headers = [
        ('Content-type', CONTENT_TYPE_LATEST),
        ('Content-Length', str(len(data)))
    ]
    start_response(status, response_headers)
    return iter([data])

Had no idea where to put this, but after some experimentation it turns out, the simplest solution of creating a metrics app looks like this (including other parts from the documentation):

app = FastAPI(debug=False)

# Preparing gunicorn multiprocessing HACKS
def make_metrics_app():
    registry = CollectorRegistry()
    multiprocess.MultiProcessCollector(registry)
    return prometheus_client.make_asgi_app(registry=registry)


metrics_app = make_metrics_app()
app.mount("/metrics", metrics_app))

Just posting this to help others save hours of researching and debugging.

IWillPull avatar May 17 '22 14:05 IWillPull

Thanks for the report! Would you be interested in adding some documentation to the README similar to how we have an example for flask, but under the multiprocess section?

csmarchbanks avatar May 17 '22 15:05 csmarchbanks

@csmarchbanks If you think this is a correct way of doing things, I will add the instructions to the README

IWillPull avatar May 18 '22 06:05 IWillPull

(me from my alt acc) Added my PR here: https://github.com/prometheus/client_python/pull/812

ashirviskas avatar May 18 '22 08:05 ashirviskas

Looking at the README I see this

It is a best practice to create this registry inside the context of a request

Doesn't this mean that we could add that setup in a route? (see below) I've seen other people also use the gunicorn when_ready hook to do the setup. Not sure what would be the best way and why.

@app.get("/metrics")
def metrics(request: Request):
    registry = CollectorRegistry()
    multiprocess.MultiProcessCollector(registry)
    data = generate_latest(registry)
    res = Response(content=data)
    res.headers["Content-Type"] = CONTENT_TYPE_LATEST
    return res

Canas avatar Jun 07 '22 21:06 Canas

Closing this as the information is now in the readme!

csmarchbanks avatar Apr 18 '23 17:04 csmarchbanks