Execution Intelligence Tool โ Bottleneck Engine + Human Report Generator
Project description
AI Cost Tracking
- ๐ค LLM usage: $1.7263 (14 commits)
- ๐ค Human dev: ~$971 (9.7h @ $100/h, 30min dedup)
Generated on 2026-05-26 using openrouter/qwen/qwen3-coder-next
metrun doesn't just show you data โ it tells you what the problem is and how to fix it.
What is metrun?
metrun is a Python performance analysis library that turns raw profiling data into an intelligible execution report: bottleneck scores, dependency graphs, critical path highlighting, and actionable fix suggestions โ all in one tool.
โ traditional profilers โ "here is your data"
โ
metrun โ "here is your problem and why it exists"
Features
| Feature | Description |
|---|---|
| ๐ง Bottleneck Engine | Builds an execution graph, computes score = time + calls + nested amplification, ranks hotspots |
| ๐ Human Report Generator | Emoji-annotated report with time %, call count, score and diagnosis per function |
| ๐งจ Critical Path | Finds the hottest nested call chain root โ leaf |
| ๐ก Fix Suggestion Engine | Library-specific advice per diagnosis: lru_cache, asyncio, numba, viztracer, scalene โฆ |
| ๐ฅ ASCII Flamegraph | Terminal-friendly proportional bar chart, zero extra dependencies |
| ๐ผ๏ธ SVG Flamegraph | Interactive SVG via flameprof |
| ๐ cProfile Bridge | Use stdlib cProfile as the profiling backend; feed results into the Bottleneck Engine |
| ๐ TOON Metric Tree | metrun scan auto-profiles and generates metrun.toon.yaml โ compact bottleneck map for the TOON ecosystem |
| โจ๏ธ CLI | metrun profile, metrun inspect, metrun scan, metrun flame commands |
Installation
pip install metrun # core (click included)
pip install metrun[flamegraph] # + SVG flamegraph support (flameprof)
Decorator tracing
from metrun import trace, get_records, analyse, print_report
@trace
def slow_query(n):
return sum(i * i for i in range(n))
@trace
def handler(items):
return [slow_query(i) for i in items]
handler(list(range(100)))
bottlenecks = analyse(get_records())
print_report(bottlenecks)
Context-manager tracing
from metrun import section, get_records, analyse, print_report
with section("data_load"):
data = load_from_db()
with section("transform"):
result = process(data)
print_report(analyse(get_records()))
Full enhanced report
from metrun import analyse, get_records, print_report
records = get_records()
bottlenecks = analyse(records)
print_report(
bottlenecks,
show_graph=True, # dependency graph
show_critical_path=True, # hottest call chain
records=records,
show_suggestions=True, # fix advice
)
Example output
๐ฅ METRUN PERFORMANCE REPORT
=============================
๐ด slow_query
โ time: 0.8200s (78.2%)
โ calls: 12,430
โ score: 12.9
โ diagnosis: ๐ฅ loop hotspot
โโ Critical Path โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐งจ Critical Path (depth=2, hottest leaf: 0.8200s)
handler [1.0500s, 1 calls]
โโ slow_query [0.8200s, 12430 calls] โ ๐ฅ hottest leaf (0.8200s)
โโ Fix Suggestions โโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ก Fix suggestions for: slow_query
1. Cache repeated results with lru_cache [functools]
from functools import lru_cache
@lru_cache(maxsize=None)
def slow_query(x): ...
2. Vectorise the loop with NumPy [numpy]
import numpy as np
result = np.sum(arr ** 2)
Auto-diagnosis labels
| Label | Trigger |
|---|---|
๐ฅ loop hotspot |
calls โฅ 1 000 |
๐ฒ dependency bottleneck |
โฅ 3 direct children in the execution graph |
๐ข slow execution |
โฅ 30 % of total wall time (time_pct โฅ 0.30), low calls |
โ
nominal |
below all thresholds |
Score formula:
score = (total_time / max_time) ร 10 + log10(calls + 1) + n_children ร 0.5
ASCII Flamegraph
from metrun import render_ascii, print_ascii
print_ascii(bottlenecks, title="My App Flamegraph")
๐ฅ My App Flamegraph
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
slow_query โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 78.2% score=12.9
handler โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 100.0% score=9.4
serialize โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 5.1% score=2.1
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
SVG Flamegraph (via flameprof)
from metrun.cprofile_bridge import CProfileBridge
from metrun import render_svg
bridge = CProfileBridge()
with bridge.profile_block():
my_function()
render_svg(bridge.get_stats(), "flame.svg")
## cProfile Bridge
Integrate with stdlib `cProfile` or any existing `.prof` dump:
```python
from metrun.cprofile_bridge import CProfileBridge
from metrun import analyse, print_report
bridge = CProfileBridge()
@bridge.profile_func
def my_function():
...
my_function()
# Analyse with the Bottleneck Engine
bottlenecks = analyse(bridge.to_records())
print_report(bottlenecks)
# Save for snakeviz / flameprof CLI
bridge.save("profile.prof")
Compatible with these popular tools (no code changes needed):
| Tool | Command |
|---|---|
| snakeviz โ interactive web viewer | snakeviz profile.prof |
| flameprof โ SVG flamegraph | flameprof profile.prof > flame.svg |
| py-spy โ sampling profiler | py-spy record -o flame.svg -- python script.py |
| viztracer โ full trace + HTML flamegraph | see below |
| scalene โ line-level CPU+memory | python -m scalene script.py |
Language-neutral records interchange
metrun can export and import normalised profiling data as JSON.
metrun profile my_script.py --export-records profile.json- saves the collected records as language-neutral JSON.
metrun inspect --records profile.json- loads a JSON or JSONL records file produced by
metrunor another runtime.
- loads a JSON or JSONL records file produced by
metrun inspect --records profile.json --export-records normalized.json- loads records, normalises them, and writes them back out as language-neutral JSON.
The importer accepts top-level records, functions, nodes, or items collections, plus single-record objects and mapping-of-records payloads. The language field is preserved when present.
Example payload:
{
"schema_version": 1,
"language": "javascript",
"records": [
{
"name": "root",
"total_time": 1.0,
"calls": 1,
"children": ["child"],
"parents": [],
"language": "javascript"
},
{
"name": "child",
"total_time": 0.25,
"calls": 4,
"children": [],
"parents": ["root"],
"language": "javascript"
}
]
}
For JSONL, write one record per line:
{"name":"root","total_time":1.0,"calls":1,"children":["child"],"language":"javascript"}
{"name":"child","total_time":0.25,"calls":4,"parents":["root"],"language":"javascript"}
pip install viztracer
from viztracer import VizTracer
with VizTracer(output_file="trace.json"): my_function()
Critical Path
from metrun import find_critical_path, print_critical_path, get_records
path = find_critical_path(get_records())
print_critical_path(path)
๐งจ Critical Path (depth=3, hottest leaf: 0.4200s)
handler [0.9100s, 1 calls]
โโ db_query [0.6300s, 50 calls]
โโ serialize [0.4200s, 50 calls] โ ๐ฅ hottest leaf (0.4200s)
Fix Suggestion Engine
from metrun import analyse, get_records, suggest, format_suggestions
for b in analyse(get_records()):
tips = suggest(b)
print(format_suggestions(b.name, tips))
Suggestion catalogue per diagnosis:
| Diagnosis | Suggestions |
|---|---|
| ๐ฅ loop hotspot | functools.lru_cache, numpy vectorisation, numba @jit |
| ๐ฒ dependency bottleneck | concurrent.futures, asyncio.gather, batching |
| ๐ข slow execution | cProfile + snakeviz, algorithmic review, joblib.Memory |
| Score โฅ 8 (any) | scalene, viztracer |
Profile a script โ bottleneck report (user code only, stdlib filtered)
metrun profile my_script.py
Profile + ASCII flamegraph in terminal
metrun profile my_script.py --ascii-flame
Profile + save SVG flamegraph
metrun profile my_script.py --flame flame.svg
Full enhanced report: bottlenecks + critical path + suggestions
metrun inspect my_script.py
Export normalised records for another runtime or later analysis
metrun profile my_script.py --export-records profile.json
Analyse language-neutral JSON or JSONL records
metrun inspect --records profile.json metrun inspect --records profile.jsonl
Load, normalise, and re-export language-neutral records
metrun inspect --records profile.json --export-records normalized.json
Include Python stdlib / C-builtins in the report
metrun profile my_script.py --include-stdlib metrun inspect my_script.py --include-stdlib
Auto-scan and generate metrun.toon.yaml metric tree
metrun scan my_script.py --output project/
Scan from pre-collected records
metrun scan --records profile.json --output project/
Convert existing .prof dump to SVG
metrun flame profile.prof -o flame.svg
---
## Automatic project scanning & TOON output
`metrun scan` profiles a Python script (or loads pre-collected records) and
generates a `metrun.toon.yaml` file containing a compact metric tree that
describes the project's performance bottlenecks.
### How it works
1. **Endpoint recognition** โ metrun identifies *root* functions (entry points)
as any function with no recorded callers. In decorator mode these are the
top-level `@trace`-d functions; in cProfile mode they are the call-tree
roots after stdlib filtering.
2. **Profiling** โ the script is executed under `cProfile` (via
`CProfileBridge`) and the resulting call tree is converted to
`FunctionRecord` entries.
3. **Bottleneck analysis** โ the `BottleneckEngine` scores every function and
assigns a diagnosis label.
4. **Critical path** โ a DFS walk finds the hottest rootโleaf chain.
5. **TOON rendering** โ all results are formatted into a compact
`.toon.yaml` file with sections: `SUMMARY`, `BOTTLENECKS`, `CRITICAL-PATH`,
`SUGGESTIONS`, `ENDPOINTS`, and `TREE`.
# metrun | 2b | top: handler ๐ฒ | python | 2026-04-07
SUMMARY:
bottlenecks: 2
top_score: 11.3
top_name: handler
top_diagnosis: ๐ฒ dependency bottleneck
total_time: 1.5500s
total_calls: 101
BOTTLENECKS[2]:
๐ฒ handler score=11.3 time=0.8000s (51.6%) calls=1 dependency bottleneck
๐ข slow_query score=10.3 time=0.7500s (48.4%) calls=100 slow execution
CRITICAL-PATH (depth=2, leaf=0.7500s):
handler โ slow_query โ ๐ฅ
SUGGESTIONS[2]:
handler: Run independent child calls concurrently [concurrent.futures]
slow_query: Profile deeper with cProfile + snakeviz [cProfile / snakeviz]
ENDPOINTS[1]:
handler calls=1 time=0.8000s children=1
TREE:
๐ฒ handler 0.8000s ร1
โ โโ ๐ข slow_query 0.7500s ร100
Python API
from metrun import analyse, get_records, generate_toon, save_toon
bottlenecks = analyse(get_records())
toon = generate_toon(bottlenecks, get_records())
save_toon(toon, "project/metrun.toon.yaml")
Integration with project.sh
metrun scan demo.py --output project/
The generated metrun.toon.yaml sits alongside other TOON files
(analysis.toon.yaml, duplication.toon.yaml, validation.toon.yaml, etc.)
and gives a performance perspective on the project.
Architecture
@trace / section() cProfile.Profile
โ โ
โผ โผ
ExecutionTracer CProfileBridge
(FunctionRecord) .to_records()
โ โ
โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโ
โผ
BottleneckEngine.analyse()
score + diagnosis + rank
โ
โโโโโโโโโโโโผโโโโโโโโโโโโโโโ
โผ โผ โผ
print_report find_critical suggest()
(report.py) _path() (suggestions.py)
ASCII/SVG flamegraph โ flamegraph.py
The two tracing backends (ExecutionTracer for decorator/section API and CProfileBridge for cProfile API) both produce the same Dict[str, FunctionRecord] structure consumed by the engine.
Module overview
metrun/
โโโ profiler.py # ExecutionTracer โ decorator + context-manager tracing
โโโ bottleneck.py # BottleneckEngine โ score, diagnosis, ranking
โโโ report.py # Human Report Generator
โโโ critical_path.py # Critical path analysis (DFS on call graph)
โโโ suggestions.py # Fix Suggestion Engine
โโโ flamegraph.py # ASCII + SVG (flameprof) flamegraphs
โโโ cprofile_bridge.py # cProfile โ metrun bridge
โโโ toon.py # TOON metric-tree generator (metrun.toon.yaml)
โโโ cli.py # Click CLI entry-point
Known limitations
| Limitation | Detail |
|---|---|
| Name collisions in cProfile mode | CProfileBridge.to_records() uses function name only as key (no file:lineno) โ functions with the same name in different modules are merged |
| Decorator tracing is opt-in | Only functions decorated with @trace or wrapped in section() appear in get_records() โ not the full call tree |
| Thread-local call stack | Each thread has an independent call stack; cross-thread parentโchild links are not recorded |
| No async support | asyncio coroutines are not automatically traced by the decorator backend |
cProfile filtering
By default CProfileBridge.to_records() and the CLI commands strip Python stdlib, C-builtins, anonymous entries (<module>, <genexpr>, etc.) and metrun's own internals โ so the report focuses on user code only. Call graph connectivity is maintained through bridging: filtered intermediate nodes (e.g. decorator wrappers) are transparently traversed when rebuilding parentโchild links.
records = bridge.to_records() # user code only (default)
records = bridge.to_records(exclude_stdlib=False) # full call tree
License
Licensed under Apache-2.0.
Status
Last updated by taskill at 2026-04-25 13:40 UTC
| Metric | Value |
|---|---|
| HEAD | f5ac1b7 |
| Coverage | โ |
| Failing tests | โ |
| Commits in last cycle | 18 |
Added documentation and examples for a configuration management system, expanded the code analysis engine and code quality metrics, and introduced profiling utilities (flamegraph, critical path, cProfile bridge) plus CLI improvements. Several docs/examples/tests were refactored and a bottleneck engine/report was merged.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file metrun-0.1.14.tar.gz.
File metadata
- Download URL: metrun-0.1.14.tar.gz
- Upload date:
- Size: 50.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3365713412e2c6c0407c4eb88fac4cb97135660a73ad471bb0f375dda74d1bd6
|
|
| MD5 |
716d5509b3d3eea0313eddc4989f8de8
|
|
| BLAKE2b-256 |
279c14d26c658c21d36de6de760e4d55a5f18f3bd18fdaa867c5981a472161a8
|
File details
Details for the file metrun-0.1.14-py3-none-any.whl.
File metadata
- Download URL: metrun-0.1.14-py3-none-any.whl
- Upload date:
- Size: 41.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
88e736850543116edc646ba7f3d7b7cb48af3a7ec27cc0fee5f37c8aaca60230
|
|
| MD5 |
3ba0cbe6cc71cd8b1c44b9f71fd277bb
|
|
| BLAKE2b-256 |
21b930d512642d0b922286d861765214820b20ab5900dd1683f026a178680594
|