In the previous post, I talked about using cProfile (via Werkzeug Application Profiler Middleware) to profile parts of code or routes in Flask web apps. In this one, we’ll explore another tool called
pyinstrument that does statistical profiling instead of deterministic profiling, that can be easily used with Flask or any other web framework like Django or FastAPI in Python.
Benefits of statistical profiling? It adds less overhead compared to something like cProfile because instead of tracking every function call (via hooks provided by Python C API), it records the call stack every 1ms (configurable). This means super fast functions that end up executing within 1ms will not get “traced” or profiled unless you configure it which is super easy.
Some of the advantages of using
- Statistical profiling.
- We can choose to profile behind flags, i.e., no need to profile each and every request. We’ll see how to do this below.
- We don’t have to use another tool (like SnakeViz) to get a graphical representation of the profiled summary or report.
pyinstrumentby itself can generate a nice HTML report of where the code spent most of its time. This report is interactive and has a good user interface.
pyinstrument is super simple:
$ pip install pyinstrument
To profile a python script all we have to do is:
$ pyinstrument script.py # Instead of python script.py
Let’s say our
script.py had the following code:
def foo(): a = 10 for _ in range(100000000): foo()
When you run the
pyinstrument command above, at the end of the script execution you’ll see a coloured summary showing how the individual and cumulative time was spent across different parts of the code (functions, methods, etc.).
You can also get a beautiful interactive HTML report by using the
$ pyinstrument -r html script.py stdout is a terminal, so saved profile output to /tmp/tmpsgobuxw2.html
Open the file in your browser of choice and you’ll see a nice interactive user interface where you can carefully analyse the time taken by different parts of the code.
Now all this is cool, but we wanted to primarily learn to how to use the profiler with our Flask web apps. We do this by using
pyinstrument‘s Python API. Basically, if we wanted to profile just a portion of our source code, the tool allows us to do that like this:
from pyinstrument import Profiler profiler = Profiler() profiler.start() # code you want to profile profiler.stop() profiler.print()
Now in the case of Flask, we can
stop() the profiler in
after_request() respectively and by the end of it, return the HTML output to the browser. Also, we can choose to run the profiler behind a flag.
from flask import Flask, g, make_response, request app = Flask(__name__) @app.before_request def before_request(): if "profile" in request.args: g.profiler = Profiler() g.profiler.start() @app.after_request def after_request(response): if not hasattr(g, "profiler"): return response g.profiler.stop() output_html = g.profiler.output_html() return make_response(output_html)
I’ve taken this piece of code from the
pyinstrument documentation. Here’s what it does:
- Start the profiler in
before_request()only when the
profilequery parameter (
?profile) is set. This query param acts like a flag to enable/disable profiling.
- The profiler instance is saved in
gwhich is a special global variable provided by Flask where you can manage resources during a request. The data on
gis lost after the context ends, i.e., after the request ends.
- Stop the profiler in
after_requestand generate the HTML output to be sent as response to the client.
Now you can interact with the profile report in your browser. In some cases, you may want to save the HTML output in a local file for future analysis.