Call stack profiler for Python that shows you why your code is slow
—
Built-in integration capabilities for popular Python frameworks and development environments including Jupyter notebooks, IPython magic commands, command-line usage, and decorator/context manager patterns for easy adoption.
Magic commands for interactive profiling in Jupyter notebooks and IPython shells with support for cell-level and line-level profiling.
def load_ipython_extension(ipython):
"""
Load pyinstrument IPython extension for magic commands.
Usage in IPython/Jupyter:
%load_ext pyinstrument
Args:
ipython: IPython instance to register magic commands with
"""
class PyinstrumentMagic:
"""
IPython magic commands for profiling code in interactive environments.
Available magics:
%pyinstrument - Profile a single line of code
%%pyinstrument - Profile an entire cell
"""
@line_cell_magic
def pyinstrument(self, line: str, cell: str | None = None):
"""
Profile code execution with various output options.
Line magic: %pyinstrument code_to_profile()
Cell magic: %%pyinstrument
code_to_profile()
more_code()
Options:
-p, --render-option: Renderer options (flag_name or option_name=option_value)
--show-regex: Regex matching file paths whose frames to always show
--show: Glob-style pattern matching file paths to show
--interval: Sampling interval in seconds (default: 0.001)
--show-all: Show all frames including root and internal IPython frames
--async_mode: Async handling mode (disabled, enabled, strict, default: disabled)
--height: Output height for HTML renderer (default: 400)
--timeline: Show timeline view (default: False)
"""Convenient decorator and context manager interfaces for easy integration with existing code.
class ProfileContext:
"""Context manager and decorator for profiling code blocks."""
def __init__(
self,
interval: float = 0.001,
async_mode: AsyncMode = "disabled",
use_timing_thread: bool | None = None,
renderer: Renderer | None = None,
target_description: str | None = None
):
"""
Initialize profiling context.
Args:
interval: Sampling interval in seconds
async_mode: Async handling mode
use_timing_thread: Use separate timing thread
renderer: Output renderer (defaults to ConsoleRenderer)
target_description: Description for profiling session
"""
def __call__(self, func: Callable | None = None, /, **kwargs):
"""
Use as decorator or create new context with updated options.
As decorator:
@profile
def my_function():
pass
With options:
@profile(interval=0.005, renderer=HTMLRenderer())
def my_function():
pass
"""
def __enter__(self):
"""Enter context manager and start profiling."""
def __exit__(self, exc_type, exc_value, traceback):
"""Exit context manager, stop profiling, and display results."""
def profile(**kwargs) -> ProfileContext:
"""
Create a profiling context manager or decorator.
Usage as context manager:
with profile():
code_to_profile()
Usage as decorator:
@profile
def function_to_profile():
pass
Args:
**kwargs: Configuration options passed to ProfileContext
Returns:
ProfileContext instance
"""Complete command-line interface for profiling Python scripts and modules.
def main():
"""
Main entry point for pyinstrument command-line interface.
Usage:
pyinstrument script.py [args...]
python -m pyinstrument script.py [args...]
Options:
--interval: Sampling interval in seconds
--async-mode: Async handling mode
--renderer: Output format (text, html, json, speedscope)
--outfile: Output file path
--show-all: Show all frames including library code
--timeline: Show timeline view (HTML renderer)
--unicode: Use Unicode characters in console output
--color: Use color in console output
"""# Load the extension
%load_ext pyinstrument
# Profile a single line
%pyinstrument expensive_function()
# Profile a cell with options
%%pyinstrument --renderer=html --render-option=timeline=true
data = load_large_dataset()
result = process_data(data)
visualize_results(result)
# Profile async code
%%pyinstrument --async_mode=enabled
await async_data_processing()from pyinstrument import profile
# Simple decorator
@profile
def data_analysis():
load_data()
process_data()
generate_report()
data_analysis() # Results automatically printed
# Decorator with options
@profile(renderer=HTMLRenderer(timeline=True))
def complex_computation():
matrix_operations()
statistical_analysis()
complex_computation()from pyinstrument import profile
from pyinstrument.renderers import HTMLRenderer
# Custom renderer context
html_renderer = HTMLRenderer(timeline=True, show_all=False)
with profile(renderer=html_renderer):
machine_learning_training()
model_evaluation()# Basic script profiling
pyinstrument my_script.py
# With options
pyinstrument --renderer=html --outfile=profile.html my_script.py
# Module profiling
python -m pyinstrument -m my_module
# With timeline view
pyinstrument --timeline --renderer=html --outfile=timeline.html script.py
# JSON output for analysis
pyinstrument --renderer=json --outfile=profile.json script.py# Django middleware example
from pyinstrument import Profiler
from django.conf import settings
class ProfilerMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if settings.DEBUG and 'profile' in request.GET:
profiler = Profiler()
profiler.start()
response = self.get_response(request)
profiler.stop()
# Add profile to response
if request.GET.get('format') == 'html':
response.content = profiler.output_html().encode()
response['Content-Type'] = 'text/html'
else:
profiler.print()
return response
return self.get_response(request)from fastapi import FastAPI, Request
from pyinstrument import Profiler
app = FastAPI()
@app.middleware("http")
async def profile_middleware(request: Request, call_next):
if request.query_params.get("profile"):
profiler = Profiler(async_mode="enabled")
profiler.start()
response = await call_next(request)
profiler.stop()
# Return profile as HTML
if request.query_params.get("format") == "html":
html = profiler.output_html()
return Response(content=html, media_type="text/html")
else:
profiler.print()
return response
return await call_next(request)from pyinstrument import Profiler
from pyinstrument.renderers import JSONRenderer
import json
def profile_and_analyze(func, *args, **kwargs):
"""Profile a function call and return structured results."""
profiler = Profiler()
profiler.start()
result = func(*args, **kwargs)
profiler.stop()
# Get structured profiling data
json_renderer = JSONRenderer()
profile_data = json.loads(json_renderer.render(profiler.last_session))
return {
'function_result': result,
'profile_data': profile_data,
'execution_time': profiler.last_session.duration,
'cpu_time': profiler.last_session.cpu_time
}
# Usage
analysis = profile_and_analyze(expensive_computation, data_param)
print(f"Execution took {analysis['execution_time']:.3f} seconds")AsyncMode = Literal["enabled", "disabled", "strict"]
class Renderer:
"""Base renderer interface for output formatting."""
def render(self, session: Session) -> str: ...
ProfileContextOptions = TypedDict("ProfileContextOptions", {
"interval": float,
"async_mode": AsyncMode,
"use_timing_thread": bool | None,
"renderer": Renderer | None,
"target_description": str | None,
}, total=False)Install with Tessl CLI
npx tessl i tessl/pypi-pyinstrument