https://github.com/semcod/metrun
https://github.com/semcod/metrun
Last synced: 8 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/semcod/metrun
- Owner: semcod
- License: apache-2.0
- Created: 2026-04-05T11:44:12.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-05-13T15:09:52.000Z (27 days ago)
- Last Synced: 2026-05-13T17:17:09.525Z (26 days ago)
- Language: Python
- Size: 1.88 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
## 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](https://openrouter.ai/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`](https://pypi.org/project/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
```bash
pip install metrun # core (click included)
pip install metrun[flamegraph] # + SVG flamegraph support (flameprof)
```
---
### Decorator tracing
```python
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
```python
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
```python
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
```python
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`)
```python
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 `metrun` or another runtime.
- `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:
```json
{
"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:
```jsonl
{"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
```python
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
```python
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
```python
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
```bash
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 (``, ``, 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.
```python
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](https://github.com/oqlos/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.