Using werkzeug profiler to determine bottlenecks in a Flask application

So you want to profile a python Flask application.  Let me save you some time…

    # Enable profiler output if FLASK_PROFILE_DIR is set
    flask_profile_dir = os.environ.get('FLASK_PROFILE_DIR', None)
    if flask_profile_dir:
        import pstats
        from werkzeug.middleware.profiler import ProfilerMiddleware
        app.wsgi_app = ProfilerMiddleware(
                            app.wsgi_app,
                            stream=None,
                            profile_dir=flask_profile_dir,
                            sort_by=pstats.SortKey.CUMULATIVE)

Drop this little block of code in your create_app() (or whatever equivalent function you’re calling) and whenever Flask is loaded with the FLASK_PROFILE_DIR environment variable set it will write profiling information to the specified directory for each call:

GET.bootstrap.static.css.bootstrap.min.css.8191ms.1610040075.prof
GET.bootstrap.static.css.fontawesome-all.min.css.8196ms.1610040075.prof
GET.bootstrap.static.jquery.min.js.8230ms.1610040075.prof

The directory must exist ahead of time since the code does not create it; a simple exercise left to the reader if you want the script to handle directory creation for you.

.prof files are not human-readable, so you’ll need to install a utility to translate them for you. SnakeViz is a pretty nice little utility to accomplish this. It will parse the .prof file, spawn a web server on localhost:8080, then open a new browser window to it for you. At the top of the page you will see the call stack:

You can click on the different options to get different views of the data. There are icicle (shown) and sunburst layouts available. Clicking on any of the colored boxes will zoom down into that portion of the call stack. Take some time to try it out and click around. There’s a lot of good info available here.

In my case, I was trying to determine why my application was taking 7-8 seconds to load a single page in a staging environment as opposed to the 200 milliseconds I’m accustomed to in my development environment. Looking at the call stack, it’s readily apparent that MongoDB calls are taking the bulk of the time. Also, note that the MongoDB calls taking so long all have sessions.py above them in the call stack. This tipped me off to look at the flask_sessions collection, which had bloated to over 1.8M documents. After drop()ing the collection, my staging site immediately sprang to life and was delivering the performance I expected. I’m still not sure how the collection got that big, but I expect it has something to do with some shitty code I wrote when hammering out the auth module originally.

Hope this helps. Let me know if you have a different method that works better for you.

Leave a Reply

Your email address will not be published. Required fields are marked *