{"id":50472321,"url":"https://github.com/semcod/metrun","last_synced_at":"2026-06-01T11:03:09.058Z","repository":{"id":349522930,"uuid":"1201951096","full_name":"semcod/metrun","owner":"semcod","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-13T15:09:52.000Z","size":1969,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-13T17:17:09.525Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/semcod.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-05T11:44:12.000Z","updated_at":"2026-05-13T15:33:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/semcod/metrun","commit_stats":null,"previous_names":["semcod/metrun"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/semcod/metrun","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semcod%2Fmetrun","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semcod%2Fmetrun/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semcod%2Fmetrun/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semcod%2Fmetrun/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/semcod","download_url":"https://codeload.github.com/semcod/metrun/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semcod%2Fmetrun/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33771630,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-01T11:03:08.358Z","updated_at":"2026-06-01T11:03:09.051Z","avatar_url":"https://github.com/semcod.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"## AI Cost Tracking\n\n![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.14-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)\n![AI Cost](https://img.shields.io/badge/AI%20Cost-$1.73-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-9.7h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)\n\n- 🤖 **LLM usage:** $1.7263 (14 commits)\n- 👤 **Human dev:** ~$971 (9.7h @ $100/h, 30min dedup)\n\nGenerated on 2026-05-26 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)\n\n---\n\n\u003e **metrun** doesn't just show you data — it tells you *what the problem is and how to fix it.*\n\n## What is metrun?\n\n`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.\n\n```\n❌ traditional profilers → \"here is your data\"\n✅ metrun               → \"here is your problem and why it exists\"\n```\n\n---\n\n## Features\n\n| Feature | Description |\n|---|---|\n| 🧠 **Bottleneck Engine** | Builds an execution graph, computes `score = time + calls + nested amplification`, ranks hotspots |\n| 📊 **Human Report Generator** | Emoji-annotated report with time %, call count, score and diagnosis per function |\n| 🧨 **Critical Path** | Finds the hottest nested call chain root → leaf |\n| 💡 **Fix Suggestion Engine** | Library-specific advice per diagnosis: `lru_cache`, `asyncio`, `numba`, `viztracer`, `scalene` … |\n| 🔥 **ASCII Flamegraph** | Terminal-friendly proportional bar chart, zero extra dependencies |\n| 🖼️ **SVG Flamegraph** | Interactive SVG via [`flameprof`](https://pypi.org/project/flameprof/) |\n| 🔌 **cProfile Bridge** | Use stdlib `cProfile` as the profiling backend; feed results into the Bottleneck Engine |\n| 📋 **TOON Metric Tree** | `metrun scan` auto-profiles and generates `metrun.toon.yaml` — compact bottleneck map for the TOON ecosystem |\n| ⌨️ **CLI** | `metrun profile`, `metrun inspect`, `metrun scan`, `metrun flame` commands |\n\n---\n\n## Installation\n\n```bash\npip install metrun            # core (click included)\npip install metrun[flamegraph] # + SVG flamegraph support (flameprof)\n```\n\n---\n\n### Decorator tracing\n\n```python\nfrom metrun import trace, get_records, analyse, print_report\n\n@trace\ndef slow_query(n):\n    return sum(i * i for i in range(n))\n\n@trace\ndef handler(items):\n    return [slow_query(i) for i in items]\n\nhandler(list(range(100)))\n\nbottlenecks = analyse(get_records())\nprint_report(bottlenecks)\n```\n\n### Context-manager tracing\n\n```python\nfrom metrun import section, get_records, analyse, print_report\n\nwith section(\"data_load\"):\n    data = load_from_db()\n\nwith section(\"transform\"):\n    result = process(data)\n\nprint_report(analyse(get_records()))\n```\n\n### Full enhanced report\n\n```python\nfrom metrun import analyse, get_records, print_report\n\nrecords = get_records()\nbottlenecks = analyse(records)\n\nprint_report(\n    bottlenecks,\n    show_graph=True,           # dependency graph\n    show_critical_path=True,   # hottest call chain\n    records=records,\n    show_suggestions=True,     # fix advice\n)\n```\n\n---\n\n## Example output\n\n```\n🔥 METRUN PERFORMANCE REPORT\n=============================\n\n🔴 slow_query\n   → time:      0.8200s  (78.2%)\n   → calls:     12,430\n   → score:     12.9\n   → diagnosis: 🔥 loop hotspot\n\n── Critical Path ─────────────────────────────\n🧨 Critical Path  (depth=2, hottest leaf: 0.8200s)\n\n  handler  [1.0500s, 1 calls]\n    └─ slow_query  [0.8200s, 12430 calls]   ← 🔥 hottest leaf (0.8200s)\n\n── Fix Suggestions ───────────────────────────\n  💡 Fix suggestions for: slow_query\n     1. Cache repeated results with lru_cache [functools]\n           from functools import lru_cache\n\n           @lru_cache(maxsize=None)\n           def slow_query(x): ...\n\n     2. Vectorise the loop with NumPy [numpy]\n           import numpy as np\n           result = np.sum(arr ** 2)\n```\n\n---\n\n## Auto-diagnosis labels\n\n| Label | Trigger |\n|---|---|\n| 🔥 `loop hotspot` | `calls ≥ 1 000` |\n| 🌲 `dependency bottleneck` | `≥ 3 direct children` in the execution graph |\n| 🐢 `slow execution` | `≥ 30 %` of total wall time (`time_pct ≥ 0.30`), low calls |\n| ✅ `nominal` | below all thresholds |\n\n**Score formula:**\n\n```\nscore = (total_time / max_time) × 10  +  log10(calls + 1)  +  n_children × 0.5\n```\n\n---\n\n## ASCII Flamegraph\n\n```python\nfrom metrun import render_ascii, print_ascii\n\nprint_ascii(bottlenecks, title=\"My App Flamegraph\")\n```\n\n```\n🔥 My App Flamegraph\n────────────────────────────────────────────────────────\n  slow_query    ████████████████████████████████████████  78.2%  score=12.9\n  handler       █████████████████████████████████████████ 100.0%  score=9.4\n  serialize     ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░   5.1%  score=2.1\n────────────────────────────────────────────────────────\n```\n\n---\n\n## SVG Flamegraph (via `flameprof`)\n\n```python\nfrom metrun.cprofile_bridge import CProfileBridge\nfrom metrun import render_svg\n\nbridge = CProfileBridge()\nwith bridge.profile_block():\n    my_function()\n\nrender_svg(bridge.get_stats(), \"flame.svg\")\n## cProfile Bridge\n\nIntegrate with stdlib `cProfile` or any existing `.prof` dump:\n\n```python\nfrom metrun.cprofile_bridge import CProfileBridge\nfrom metrun import analyse, print_report\n\nbridge = CProfileBridge()\n\n@bridge.profile_func\ndef my_function():\n    ...\n\nmy_function()\n\n# Analyse with the Bottleneck Engine\nbottlenecks = analyse(bridge.to_records())\nprint_report(bottlenecks)\n\n# Save for snakeviz / flameprof CLI\nbridge.save(\"profile.prof\")\n```\n\nCompatible with these popular tools (no code changes needed):\n\n| Tool | Command |\n|---|---|\n| **snakeviz** — interactive web viewer | `snakeviz profile.prof` |\n| **flameprof** — SVG flamegraph | `flameprof profile.prof \u003e flame.svg` |\n| **py-spy** — sampling profiler | `py-spy record -o flame.svg -- python script.py` |\n| **viztracer** — full trace + HTML flamegraph | see below |\n| **scalene** — line-level CPU+memory | `python -m scalene script.py` |\n\n---\n\n## Language-neutral records interchange\n\n`metrun` can export and import normalised profiling data as JSON.\n\n- `metrun profile my_script.py --export-records profile.json`\n  - saves the collected records as language-neutral JSON.\n- `metrun inspect --records profile.json`\n  - loads a JSON or JSONL records file produced by `metrun` or another runtime.\n- `metrun inspect --records profile.json --export-records normalized.json`\n  - loads records, normalises them, and writes them back out as language-neutral JSON.\n\nThe 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.\n\nExample payload:\n\n```json\n{\n  \"schema_version\": 1,\n  \"language\": \"javascript\",\n  \"records\": [\n    {\n      \"name\": \"root\",\n      \"total_time\": 1.0,\n      \"calls\": 1,\n      \"children\": [\"child\"],\n      \"parents\": [],\n      \"language\": \"javascript\"\n    },\n    {\n      \"name\": \"child\",\n      \"total_time\": 0.25,\n      \"calls\": 4,\n      \"children\": [],\n      \"parents\": [\"root\"],\n      \"language\": \"javascript\"\n    }\n  ]\n}\n```\n\nFor JSONL, write one record per line:\n\n```jsonl\n{\"name\":\"root\",\"total_time\":1.0,\"calls\":1,\"children\":[\"child\"],\"language\":\"javascript\"}\n{\"name\":\"child\",\"total_time\":0.25,\"calls\":4,\"parents\":[\"root\"],\"language\":\"javascript\"}\n```\n\n---\n\n# pip install viztracer\nfrom viztracer import VizTracer\n\nwith VizTracer(output_file=\"trace.json\"):\n    my_function()\n\n## Critical Path\n\n```python\nfrom metrun import find_critical_path, print_critical_path, get_records\n\npath = find_critical_path(get_records())\nprint_critical_path(path)\n```\n\n```\n🧨 Critical Path  (depth=3, hottest leaf: 0.4200s)\n\n  handler  [0.9100s, 1 calls]\n    └─ db_query  [0.6300s, 50 calls]\n      └─ serialize  [0.4200s, 50 calls]   ← 🔥 hottest leaf (0.4200s)\n```\n\n---\n\n## Fix Suggestion Engine\n\n```python\nfrom metrun import analyse, get_records, suggest, format_suggestions\n\nfor b in analyse(get_records()):\n    tips = suggest(b)\n    print(format_suggestions(b.name, tips))\n```\n\nSuggestion catalogue per diagnosis:\n\n| Diagnosis | Suggestions |\n|---|---|\n| 🔥 loop hotspot | `functools.lru_cache`, `numpy` vectorisation, `numba @jit` |\n| 🌲 dependency bottleneck | `concurrent.futures`, `asyncio.gather`, batching |\n| 🐢 slow execution | `cProfile + snakeviz`, algorithmic review, `joblib.Memory` |\n| Score ≥ 8 (any) | `scalene`, `viztracer` |\n\n---\n\n# Profile a script — bottleneck report (user code only, stdlib filtered)\nmetrun profile my_script.py\n\n# Profile + ASCII flamegraph in terminal\nmetrun profile my_script.py --ascii-flame\n\n# Profile + save SVG flamegraph\nmetrun profile my_script.py --flame flame.svg\n\n# Full enhanced report: bottlenecks + critical path + suggestions\nmetrun inspect my_script.py\n\n# Export normalised records for another runtime or later analysis\nmetrun profile my_script.py --export-records profile.json\n\n# Analyse language-neutral JSON or JSONL records\nmetrun inspect --records profile.json\nmetrun inspect --records profile.jsonl\n\n# Load, normalise, and re-export language-neutral records\nmetrun inspect --records profile.json --export-records normalized.json\n\n# Include Python stdlib / C-builtins in the report\nmetrun profile my_script.py --include-stdlib\nmetrun inspect my_script.py --include-stdlib\n\n# Auto-scan and generate metrun.toon.yaml metric tree\nmetrun scan my_script.py --output project/\n\n# Scan from pre-collected records\nmetrun scan --records profile.json --output project/\n\n# Convert existing .prof dump to SVG\nmetrun flame profile.prof -o flame.svg\n```\n\n---\n\n## Automatic project scanning \u0026 TOON output\n\n`metrun scan` profiles a Python script (or loads pre-collected records) and\ngenerates a `metrun.toon.yaml` file containing a compact metric tree that\ndescribes the project's performance bottlenecks.\n\n### How it works\n\n1. **Endpoint recognition** — metrun identifies *root* functions (entry points)\n   as any function with no recorded callers.  In decorator mode these are the\n   top-level `@trace`-d functions; in cProfile mode they are the call-tree\n   roots after stdlib filtering.\n2. **Profiling** — the script is executed under `cProfile` (via\n   `CProfileBridge`) and the resulting call tree is converted to\n   `FunctionRecord` entries.\n3. **Bottleneck analysis** — the `BottleneckEngine` scores every function and\n   assigns a diagnosis label.\n4. **Critical path** — a DFS walk finds the hottest root→leaf chain.\n5. **TOON rendering** — all results are formatted into a compact\n   `.toon.yaml` file with sections: `SUMMARY`, `BOTTLENECKS`, `CRITICAL-PATH`,\n   `SUGGESTIONS`, `ENDPOINTS`, and `TREE`.\n\n# metrun | 2b | top: handler 🌲 | python | 2026-04-07\n\nSUMMARY:\n  bottlenecks: 2\n  top_score: 11.3\n  top_name: handler\n  top_diagnosis: 🌲 dependency bottleneck\n  total_time: 1.5500s\n  total_calls: 101\n\nBOTTLENECKS[2]:\n  🌲 handler                        score=11.3   time=0.8000s (51.6%)  calls=1       dependency bottleneck\n  🐢 slow_query                     score=10.3   time=0.7500s (48.4%)  calls=100     slow execution\n\nCRITICAL-PATH (depth=2, leaf=0.7500s):\n  handler → slow_query ← 🔥\n\nSUGGESTIONS[2]:\n  handler: Run independent child calls concurrently [concurrent.futures]\n  slow_query: Profile deeper with cProfile + snakeviz [cProfile / snakeviz]\n\nENDPOINTS[1]:\n  handler  calls=1  time=0.8000s  children=1\n\nTREE:\n  🌲 handler  0.8000s  ×1\n  │ ├─ 🐢 slow_query  0.7500s  ×100\n```\n\n### Python API\n\n```python\nfrom metrun import analyse, get_records, generate_toon, save_toon\n\nbottlenecks = analyse(get_records())\ntoon = generate_toon(bottlenecks, get_records())\nsave_toon(toon, \"project/metrun.toon.yaml\")\n```\n\n### Integration with project.sh\n\n```bash\nmetrun scan demo.py --output project/\n```\n\nThe generated `metrun.toon.yaml` sits alongside other TOON files\n(`analysis.toon.yaml`, `duplication.toon.yaml`, `validation.toon.yaml`, etc.)\nand gives a performance perspective on the project.\n\n---\n\n## Architecture\n\n```\n  @trace / section()          cProfile.Profile\n       │                            │\n       ▼                            ▼\n ExecutionTracer              CProfileBridge\n  (FunctionRecord)             .to_records()\n       │                            │\n       └──────────┬─────────────────┘\n                  ▼\n         BottleneckEngine.analyse()\n          score + diagnosis + rank\n                  │\n       ┌──────────┼──────────────┐\n       ▼          ▼              ▼\n  print_report  find_critical  suggest()\n  (report.py)    _path()      (suggestions.py)\n                            \n  ASCII/SVG flamegraph ← flamegraph.py\n```\n\nThe 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.\n\n## Module overview\n\n```\nmetrun/\n├── profiler.py        # ExecutionTracer — decorator + context-manager tracing\n├── bottleneck.py      # BottleneckEngine — score, diagnosis, ranking\n├── report.py          # Human Report Generator\n├── critical_path.py   # Critical path analysis (DFS on call graph)\n├── suggestions.py     # Fix Suggestion Engine\n├── flamegraph.py      # ASCII + SVG (flameprof) flamegraphs\n├── cprofile_bridge.py # cProfile ↔ metrun bridge\n├── toon.py            # TOON metric-tree generator (metrun.toon.yaml)\n└── cli.py             # Click CLI entry-point\n```\n\n## Known limitations\n\n| Limitation | Detail |\n|---|---|\n| **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 |\n| **Decorator tracing is opt-in** | Only functions decorated with `@trace` or wrapped in `section()` appear in `get_records()` — not the full call tree |\n| **Thread-local call stack** | Each thread has an independent call stack; cross-thread parent→child links are not recorded |\n| **No async support** | `asyncio` coroutines are not automatically traced by the decorator backend |\n\n## cProfile filtering\n\nBy default `CProfileBridge.to_records()` and the CLI commands strip Python stdlib, C-builtins, anonymous entries (`\u003cmodule\u003e`, `\u003cgenexpr\u003e`, 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.\n\n```python\nrecords = bridge.to_records()                    # user code only (default)\nrecords = bridge.to_records(exclude_stdlib=False) # full call tree\n```\n\n## License\n\nLicensed under Apache-2.0.\n## Status\n\n_Last updated by [taskill](https://github.com/oqlos/taskill) at 2026-04-25 13:40 UTC_\n\n| Metric | Value |\n|---|---|\n| HEAD | `f5ac1b7` |\n| Coverage | — |\n| Failing tests | — |\n| Commits in last cycle | 18 |\n\n\u003e 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.\n\n\u003c!-- taskill:status:end --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemcod%2Fmetrun","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsemcod%2Fmetrun","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemcod%2Fmetrun/lists"}