Let's say we're running a Flask application on Gunicorn (because we're cool like that). We've got a few endpoints, and one of them is acting up. Time to put on our py-spy goggles and see what's what.

Our Suspect: The CPU-Hungry Handler

Here's a simple Flask app with a handler that's clearly up to no good:


from flask import Flask
import time

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

@app.route('/cpu_hog')
def cpu_hog():
    # This is where the magic (read: problem) happens
    result = 0
    for i in range(10000000):
        result += i
    return f"I counted to {result}. Aren't you proud?"

if __name__ == '__main__':
    app.run()

Spoiler alert: That /cpu_hog endpoint is our prime suspect.

Enter py-spy: Our Profiling Sidekick

First things first, let's get py-spy on our team:


pip install py-spy

Now, let's fire up our Gunicorn server:


gunicorn app:app -w 4

Here comes the fun part. In another terminal, let's unleash py-spy on our unsuspecting WSGI server:


sudo py-spy record -o profile.svg --pid $(pgrep -f gunicorn) --subprocesses
Pro Tip: We're using sudo here because py-spy needs to attach to the process. Be careful with sudo powers, though. With great power comes... well, you know the rest.

Decoding the Profiling Results: CSI: CPU Edition

After hitting our /cpu_hog endpoint a few times (go ahead, we'll wait), let's take a look at that beautiful SVG flame graph py-spy generated for us.

Flame Graph
Our CPU usage flame graph. It's like a crime scene, but for code.

What do we see? A towering inferno of CPU usage in our cpu_hog function! It's like spotting Waldo, if Waldo were a performance bottleneck wearing a striped shirt of inefficiency.

Breaking Down the Flame Graph

  • The width of each bar represents the time spent in that function
  • The colors? They're just pretty. Don't read too much into them.
  • Stacked bars show the call stack. It's like a sandwich of slowness.

The Plot Thickens: Analyzing Our Findings

So, what have we learned from our py-spy adventure?

  1. Our cpu_hog function is living up to its name. It's hogging CPU like it's going out of style.
  2. The culprit? That innocent-looking for loop. It's doing more iterations than a washing machine stuck on spin cycle.
  3. Our other endpoints (like hello_world) are barely visible. They're the unsung heroes of our app.

Plot Twist: Optimizing Our CPU Hog

Now that we've caught our performance culprit red-handed, let's reform it:


@app.route('/cpu_hog_reformed')
def cpu_hog_reformed():
    # Let's use a more efficient way to sum numbers
    result = sum(range(10000001))
    return f"I efficiently counted to {result}. Much better, right?"

Run py-spy again with this new endpoint, and voilà! Our flame graph should look less like the towering inferno and more like a cozy campfire.

Lessons Learned: The Py-spy Profiling Playbook

What pearls of wisdom can we take away from this profiling escapade?

  • Trust, but verify: Even simple-looking code can be a performance nightmare. Always profile before optimizing.
  • py-spy is your friend: It's non-intrusive, fast, and gives you a visual representation of your CPU usage. What's not to love?
  • Think algorithmically: Sometimes, the best optimization is using a more efficient algorithm. Big O notation isn't just for whiteboard interviews!
  • WSGI servers are complex beasts: Remember, we're not just profiling our app, but the entire WSGI ecosystem. It's turtles all the way down!

The Epilogue: Keep Calm and Profile On

Profiling with py-spy is like giving your code a health check-up. It might reveal some uncomfortable truths, but in the end, your application will thank you. And remember, every millisecond counts when you're serving web requests!

So, the next time your Python WSGI server starts acting up, don't panic. Grab py-spy, generate those flame graphs, and start hunting those CPU hogs. Your users (and your boss) will thank you.

Food for Thought: What other parts of your application could benefit from a py-spy profiling session? Database queries? External API calls? The possibilities are endless!

Now go forth and profile, you magnificent code detective, you!