pyinstrument

repository·main·Indexed 25 days ago

https://github.com/joerick/pyinstrument

A statistical Python profiler for Python 3.8+ designed to identify slow code sections. It provides interactive HTML reports and supports async code, Django middleware, IPython/Jupyter notebooks, and integration with frameworks like Flask, FastAPI, Falcon, and aiohttp.web. Unlike tracing profilers, pyinstrument uses a sampling mechanism to reduce overhead and records wall-clock time to capture I/O operations.

Tokens
6.2K
Snippets
15
Records
37
Agent score
93%

What's inside pyinstrument

  1. Understand pyinstrument's statistical profiling mechanism

    main

    Pyinstrument is a statistical profiler, not a tracing profiler. Instead of tracking every function call, it interrupts the program at a configured interval (defaulting to 1ms) and records the entire call stack.

    Key advantages:

    • Low Overhead: Significantly lower overhead compared to tracing profilers like cProfile or profile, which can distort results by slowing down code that makes frequent function calls.
    • Accuracy: Even with fewer samples, accuracy is maintained because long-running function calls are recorded at the end of the call, effectively 'bunching' samples.
    • Full-stack Recording: Unlike cProfile which provides a flat list of functions, pyinstrument records the entire stack, making it easier to understand the context of why expensive calls are being made. It also hides library frames by default to focus on your application code.
  2. Use pyinstrument in IPython or Jupyter Notebooks

    main

    To use pyinstrument natively in an IPython notebook:

    1. Load the extension at the top of your notebook: %load_ext pyinstrument.
    2. Use the magic command in the cell you want to profile: %%pyinstrument.
  3. Profile a Python script via CLI

    main

    Run Pyinstrument directly from the command line to profile a Python script. Instead of using python script.py, use pyinstrument script.py. The tool will output a colored summary of time consumption upon completion or when interrupted with ^C.

    Use the -r html flag to generate an interactive HTML profile report for deeper exploration.

  4. Profile specific Pytest tests using a fixture

    main

    To generate individual HTML profiling reports for each test, create an autouse fixture in your conftest.py file. This approach automatically starts the Profiler before each test and writes the results to a .profiles directory after the test completes.

    from pathlib import Path
    import pytest
    from pyinstrument import Profiler
    
    TESTS_ROOT = Path.cwd()
    
    @pytest.fixture(autouse=True)
    def auto_profile(request):
        PROFILE_ROOT = (TESTS_ROOT / ".profiles")
        # Turn profiling on
        profiler = Profiler()
        profiler.start()
    
        yield  # Run test
    
        profiler.stop()
        PROFILE_ROOT.mkdir(exist_ok=True)
        results_file = PROFILE_ROOT / f"{request.node.name}.html"
        profiler.write_html(results_file)
  5. Profile code in Jupyter or IPython

    main

    Use IPython magics to profile lines or entire cells in Jupyter notebooks or IPython shells.

    1. Load the extension: %load_ext pyinstrument

    2. Use the %%pyinstrument magic at the top of a cell to profile it.

    %load_ext pyinstrument
    
    %%pyinstrument
    import time
    # ... code to profile ...
  6. Configure Django middleware

    main

    When using the Django middleware, you can use the following environment variables and options:

    • PYINSTRUMENT_INTERVAL: Set the sampling interval for the middleware.
    • PYINSTRUMENT_PROFILE_DIR: Log profiles of all requests to a specified folder.
    • PYINSTRUMENT_USE_SIGNAL: Use signal mode if the default mode presents problems.
    • PYINSTRUMENT_SHOW_CALLBACK: Add a condition to decide whether to show the profile (useful for live servers).
    • PYINSTRUMENT_PROFILE_DIR_RENDERER: Configure the renderer for the Django middleware file output.

    Note: Recent versions also allow customizing the filename of saved profile runs via a callback in the Django configuration.

  7. Configure Pyinstrument middleware for Django

    main

    To profile Django web requests, add pyinstrument.middleware.ProfilerMiddleware to your MIDDLEWARE setting in settings.py.

    Activation

    Append ?profile to any request URL to view the analysis in a web page instead of the standard response.

    Configuration Options

    • PYINSTRUMENT_PROFILE_DIR: Set a directory (e.g., 'profiles') to save all profiled requests as HTML files automatically.
    • PYINSTRUMENT_FILENAME: Define a custom filename format (default: "{total_time:.3f}s {path} {timestamp:.0f}.{ext}").
    • PYINSTRUMENT_FILENAME_CALLBACK: Provide a callback function that returns a filename string for advanced control.
    • PYINSTRUMENT_SHOW_CALLBACK: Provide a dotted path to a function callback(request) -> bool to control which requests trigger the profiling page.
    • PYINSTRUMENT_PROFILE_DIR_RENDERER: Set the output renderer. Supported: pyinstrument.renderers.JSONRenderer, pyinstrument.renderers.HTMLRenderer, pyinstrument.renderers.SpeedscopeRenderer.
    • PYINSTRUMENT_INTERVAL: Set the sampling interval (default: 0.001).
  8. Known issues and troubleshooting

    main

    Docker

    Profiling code inside a Docker container can cause strange results because the gettimeofday syscall used by pyinstrument can be slow in those environments.

    Pickle and main

    When running pyinstrument script.py where script.py contains a class serialized with pickle, you might encounter errors because the serialization machinery doesn't know where __main__ is.

  9. Profile web requests in FastAPI

    main

    Use an async middleware to profile FastAPI requests.

    Note: This approach only profiles async def path operation functions. Routes defined with standard def are executed in a separate thread and will not be captured by this middleware.

    To invoke, add the GET parameter profile=1 to your request.

    from fastapi import Request
    from fastapi.responses import HTMLResponse
    from pyinstrument import Profiler
    
    @app.middleware("http")
    async def profile_request(request: Request, call_next):
        profiling = request.query_params.get("profile", False)
        if profiling:
            profiler = Profiler()
            profiler.start()
            await call_next(request)
            profiler.stop()
            return HTMLResponse(profiler.output_html())
        else:
            return await call_next(request)