Architecture
How myflames works internally: parser, renderers, advisor, and the teach module.
Design principles
- Zero external dependencies — Python 3.7+ stdlib only. No pip install of MySQL drivers, templating engines, or JS frameworks.
- Single parser, multiple renderers — JSON is parsed once into a unified tree. Renderers never re-parse JSON.
- Offline-first output — every HTML file is self-contained. No external scripts, stylesheets, or fonts. Works in Slack DMs, email attachments, and air-gapped environments.
- MySQL + MariaDB parity — the parser auto-detects the engine and normalizes MariaDB's different JSON structure into the same internal tree.
Data flow
Module map
| Module | Role |
|---|---|
cli.py | Argument parsing, live-connection orchestration, SVG height patching for the analysis panel. |
parser.py | Single entry point. Builds unified tree from MySQL or MariaDB JSON. analyze_plan(root) scans for full scans, hash joins, temp tables, filesorts, non-sargable joins, etc. |
flamegraph.py | SVG flame graph renderer (pure-Python port of Brendan Gregg's FlameGraph). |
output_bargraph.py | SVG bar chart renderer, sorted by self-time. |
output_treemap.py | SVG treemap renderer with squarified layout. |
output_diagram.py | SVG Visual Explain-style diagram with drag/zoom. |
output_tree.py | SVG collapsible execution tree. |
output_html.py | HTML wrapper with progressive-disclosure UI, glossary chips, and JSON-LD in <head>. |
output_sidecar.py | JSON sidecar generator (v1 schema). Machine-readable plan summary, warnings, suggestions. |
advisor.py | Environment advisor: 8 rules that combine plan signals with collected server state. |
glossary.py | 31 glossary entries with 3-tier explanations (short / technical / newcomer). Powers the HTML glossary chips. |
teach/ | Interactive algorithm lessons. Shared animation runtime + cost models + per-lesson renderers. |
Parser internals
The parser (parser.py) handles two structurally different JSON formats:
- MySQL 8.4+ uses an
operation/inputs[]tree structure (format version 2). - MariaDB 10.11+ uses a
query_block/nested_loop/tablestructure.
MariaDB normalization (_normalize_mariadb*() functions) converts MariaDB's structure into MySQL's operation/inputs tree before parse_node() processes it. This means every renderer and the advisor work with one canonical tree format.
analyze_plan(root) walks the parsed tree and returns a dictionary of detected features: full_scans, hash_joins, temp_tables, filesorts, bnl_nodes, nonsargable_joins, index_suggestions, and more.
Teach module architecture
The teach/ subpackage is a self-contained animation platform:
| File | Role |
|---|---|
__init__.py | Lesson registry (LESSONS dict), render_lesson(), CLI dispatch. |
_anim.py | Shared JS animation runtime: tween, timeline, easing, pause/speed, scrubber, phase marks. |
_html.py | Shared HTML chrome: CSS, controls, toolbar, phase nav, query card, explainer, readout grid. |
_cost_model.py | Cost-model functions tied to MySQL 8.4 / MariaDB 11.4 defaults. Constants enforced by tests. |
join_family/, index_family/, scan_family/, cache_family/ | Lesson modules grouped by topic. Each exports a render() function. Some lessons remain as top-level *.py files with thin family wrappers. |
Every lesson is a single self-contained HTML file (~65-85 KB) with inlined CSS, JS, and SVG. No build step, no bundler, no CI.
Environment advisor rules
advisor.py runs 8 rules that cross-reference the parsed plan with collected server state:
| Rule | Fires when |
|---|---|
| Non-sargable join predicate | Join uses CONCAT(col), CAST(col), LOWER(col), DATE(col), etc. |
| Buffer pool vs working set | innodb_buffer_pool_size < 25-50% of referenced tables |
| Sort buffer vs filesort | Filesort detected + sort_buffer_size < 2 MB |
| Join buffer vs hash/BNL | Hash join or BNL + join_buffer_size < 2 MB |
| Tmp table size | Temp table + min(tmp_table_size, max_heap_table_size) < 32 MB |
| Optimizer switch overrides | hash_join=off, mrr=off, derived_condition_pushdown=off |
| Missing indexes | Parser heuristic + collected schema confirms no covering index |
| Engine != InnoDB | Table uses MyISAM/MEMORY |
Every suggestion carries a Why: clause grounded in the MySQL cost model. This is enforced by a test.
Testing
The test suite has 1200+ tests across two files:
test/test_myflames.py— parser, renderers, advisor, HTML wrapper, sidecar, compare, live-connection modetest/test_teach.py— cost-model invariants, version-accuracy strings, CLI scaffolding, shared runtime markers
# Full suite
./run-tests.sh
# Just teach tests
python3 -m unittest discover -s test -p "test_teach.py" -v
Fixtures are generated from live MySQL/MariaDB Docker containers:
./scripts/generate-fixtures.sh # MySQL fixtures
./scripts/generate-mariadb-fixtures.sh # MariaDB fixtures