executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
performance-profilingIdentify computational bottlenecks, analyze parallel scaling, estimate memory requirements, and generate optimization recommendations for materials simulations — parse timing logs to find dominant phases (solver, assembly, I/O), evaluate strong and weak scaling efficiency, profile memory from mesh and field parameters, and detect bottlenecks with actionable…
Independent GitHub snapshot · Automated classification · Original documentation governs
Provide tools to analyze simulation performance, identify bottlenecks, and recommend optimization strategies for computational materials science simulations.
Before running profiling scripts, collect from the user:
| Input | Description | Example |
|---|---|---|
| Simulation log | Log file with timing information | simulation.log |
| Scaling data | JSON with multi-run performance data | scaling_data.json |
| Simulation parameters | JSON with mesh, fields, solver config | params.json |
| Available memory | System memory in GB (optional) | 16.0 |
Need to identify slow phases?
├── YES → Use timing_analyzer.py
│ └── Parse simulation logs for timing data
│
Need to understand parallel performance?
├── YES → Use scaling_analyzer.py
│ └── Analyze strong or weak scaling efficiency
│
Need to estimate memory requirements?
├── YES → Use memory_profiler.py
│ └── Estimate memory from problem parameters
│
Need optimization recommendations?
└── YES → Use bottleneck_detector.py
└── Combine analyses and get actionable advice
| Metric | Good | Acceptable | Poor |
|---|---|---|---|
| Phase dominance | <30% | 30-50% | >50% |
| Parallel efficiency | >0.80 | 0.70-0.80 | <0.70 |
| Memory usage | <60% | 60-80% | >80% |
All scripts wrap their payload in a top-level object with two keys: inputs and results. The fields below live under results.
| Script | Key Outputs (under results) |
|---|---|
timing_analyzer.py | results.phases, results.slowest_phase, results.total_time |
scaling_analyzer.py | results.results, results.efficiency_threshold_processors, results.average_efficiency, results.baseline |
memory_profiler.py | results.total_memory_gb, results.per_process_gb, results.field_memory_gb, results.solver_workspace_gb, results.matrix_storage_gb, results.warnings |
bottleneck_detector.py | results.bottlenecks, results.recommendations |
# Basic timing analysis
python3 scripts/timing_analyzer.py \
--log simulation.log \
--json
# Custom timing pattern
python3 scripts/timing_analyzer.py \
--log simulation.log \
--pattern 'Step\s+(\w+)\s+took\s+([\d.]+)s' \
--json
# Strong scaling (fixed problem size)
python3 scripts/scaling_analyzer.py \
--data scaling_data.json \
--type strong \
--json
# Weak scaling (constant work per processor)
python3 scripts/scaling_analyzer.py \
--data scaling_data.json \
--type weak \
--json
# Estimate memory requirements
python3 scripts/memory_profiler.py \
--params simulation_params.json \
--available-gb 16.0 \
--json
# Detect bottlenecks from timing only
python3 scripts/bottleneck_detector.py \
--timing timing_results.json \
--json
# Comprehensive analysis with all inputs
python3 scripts/bottleneck_detector.py \
--timing timing_results.json \
--scaling scaling_results.json \
--memory memory_results.json \
--json
User: My simulation is taking too long. Can you help me identify what's slow?
Agent workflow:
python3 scripts/timing_analyzer.py --log simulation.log --json
python3 scripts/scaling_analyzer.py --data scaling.json --type strong --json
python3 scripts/bottleneck_detector.py --timing timing.json --scaling scaling.json --json
The detector applies per-type dominance thresholds: solver/assembly/general phases are flagged above 50% of runtime; I/O phases above 30%. Any flagged phase above 70% is reported as high severity.
| Scenario | Meaning | Action |
|---|---|---|
| Solver >50% (high >70%) | Solver-dominated | Tune preconditioner, check tolerance |
| Assembly >50% | Assembly-dominated | Cache matrices, vectorize, parallelize |
| I/O >30% | I/O-dominated | Reduce frequency, use parallel I/O |
| Balanced (below thresholds) | Well-balanced | Look for algorithmic improvements |
| Efficiency | Meaning | Action |
|---|---|---|
| >0.80 | Excellent scaling | Continue scaling up |
| 0.70-0.80 | Good scaling | Monitor at larger scales |
| 0.50-0.70 | Poor scaling | Investigate communication/load balance |
| <0.50 | Very poor scaling | Reduce processor count or redesign |
| Usage | Meaning | Action |
|---|---|---|
| <60% available | Safe | No action needed |
| 60-80% available | Moderate | Monitor, consider optimization |
| >80% available | High | Reduce resolution or increase processors |
| >100% available | Exceeds capacity | Must reduce problem size |
The estimate follows the three-term formula Total = Field + Solver Workspace + Matrix Storage (see references/profiling_guide.md). Matrix storage and solver workspace depend on solver.type:
iterative (default): sparse matrix (default 7-point stencil, override via solver.stencil_nnz) plus workspace vectors.direct: sparse matrix scaled by a conservative fill-in factor (solver.fillin_factor, default 10) to reflect factorization fill-in — a direct solver estimates far more memory than an iterative one for the same mesh.matrix-free: no assembled matrix; workspace vectors only.The estimate is intentionally conservative so a "will it fit in RAM?" decision does not silently under-estimate.
| Error | Cause | Resolution |
|---|---|---|
Log file not found | Invalid path | Verify log file path |
No timing data found | Pattern mismatch | Provide custom pattern with --pattern |
At least 2 runs required | Insufficient data | Provide more scaling runs |
Missing required parameters | Incomplete params | Add mesh and fields to params file |
Before trusting a profiling result or acting on a recommendation, record the concrete evidence below:
timing_analyzer.py actually matched entries — results.phases is non-empty and results.total_time > 0; if a custom --pattern was used and results.message/suggested_patterns appeared, the pattern was fixed and re-run (an empty phases list silently looks like a fast simulation).phases[].percentage is ~100% and that named phases cover the wall-clock time — unaccounted-for time means missing log lines, not a balanced run.results.average_efficiency and results.efficiency_threshold_processors from scaling_analyzer.py; verified the --type (strong vs weak) matches how the runs were generated (fixed total size vs fixed work-per-rank).memory_profiler.py (field_memory_gb, solver_workspace_gb, matrix_storage_gb, total_memory_gb) and confirmed solver.type (iterative / direct / matrix-free) matches the real solver — a direct solve carries the ~10x fill-in factor and a wrong type makes the "fits in RAM?" answer unsafe.results.warnings and compared total_memory_gb (and per_process_gb) against the actual --available-gb; treated >80% as the documented "high" band, not a pass.bottleneck_detector.py recommendation, confirmed the driving bottleneck (its category, value, and threshold) is consistent with the timing/scaling/memory inputs that were actually supplied — recommendations only reflect the JSON files passed via --timing/--scaling/--memory.value to confirm the bottleneck actually moved (re-profile step of the workflow).| Tempting shortcut | Why it's wrong / what to do |
|---|---|
"timing_analyzer.py returned no bottlenecks, so the run is balanced." | An empty/low result is often a pattern mismatch — phases may be empty or partial. Verify total_time matches wall-clock and that phase percentages sum to ~100% before concluding "balanced". |
| "Two runs scaled fine, so it scales." | Two points only give an average efficiency; they cannot reveal where efficiency falls off. Add more processor counts and check efficiency_threshold_processors, and confirm you used the correct --type (strong vs weak). |
| "Iterative vs direct is just a flag; memory is about the same." | memory_profiler.py applies a conservative ~10x fill-in factor for direct and stores no matrix for matrix-free. Setting the wrong solver.type can under-estimate RAM by an order of magnitude — set it to the real solver. |
"It fits in --available-gb total, so we're fine." | The relevant number for an MPI run is per_process_gb against per-node/per-rank RAM, and >80% of total already triggers a warning. Check the per-process figure and the warnings list, not just the total. |
| "I/O is under 50%, so I/O isn't the bottleneck." | I/O is flagged at the lower 30% threshold, not 50%. A 30-50% I/O phase is a real bottleneck the detector reports — reduce output frequency or use parallel I/O. |
| "The recommendation says tune the preconditioner, so the solver is the problem." | Recommendations are only as complete as the JSON you passed in. If --scaling/--memory were omitted, those bottlenecks are simply invisible — feed all available analyses before trusting the priority ranking. |
--pattern regex values are validated for length (500 chars max) and rejected if they contain constructs prone to catastrophic backtracking (ReDoS)available_gb is validated as a positive finite number; mesh dimensions and field parameters are validated as positive integers--type (scaling type) is validated against a fixed allowlist (strong, weak)timing_analyzer.py reads a single log file specified by --log; log files are capped at 500 MB and rejected before parsingscaling_analyzer.py, memory_profiler.py, and bottleneck_detector.py read JSON files capped at 100 MBallowed-tools excludes Bash to prevent the agent from executing arbitrary commands when processing untrusted simulation logs or result fileseval(), exec(), or dynamic code generationshell=True)references/profiling_guide.md - Profiling concepts and interpretationreferences/optimization_strategies.md - Detailed optimization approachesSee CHANGELOG.md for the authoritative, dated release history.