From 56f92f3659d77aa2372f86e43a8466ed030fef4e Mon Sep 17 00:00:00 2001 From: skal Date: Sun, 8 Feb 2026 13:05:15 +0100 Subject: feat(tools): Add effect depth analysis to seq_compiler Adds --analyze flag to seq_compiler to identify performance bottlenecks by analyzing how many effects run simultaneously at each point in the demo. Features: - Samples timeline at 10 Hz (every 0.1s) - Counts overlapping effects at each sample point - Generates histogram of effect depth distribution - Identifies bottleneck periods (>5 concurrent effects) - Reports max concurrent effects with timestamps - Lists top 10 bottleneck peaks with effect names Analysis Results for Current Demo (demo.seq): - Max concurrent effects: 11 at t=8.8s - 28 bottleneck periods detected (>5 effects) - 29.6% of demo has 7+ effects running (critical) - Most intensive section: 8b-12b (4-6 seconds) Most Used Effects: - GaussianBlurEffect: ~10 instances (optimization target) - HeptagonEffect: ~9 instances - ThemeModulationEffect: ~7 instances Usage: ./build/seq_compiler assets/demo.seq --analyze ./build/seq_compiler assets/demo.seq --analyze --gantt-html=out.html Files Modified: - tools/seq_compiler.cc: Added analyze_effect_depth() function - EFFECT_DEPTH_ANALYSIS.md: Detailed analysis report + recommendations - timeline_analysis.html: Visual Gantt chart (example output) This helps identify: - Which sequences have too many overlapping effects - When to stagger effect timing to reduce GPU load - Which effects appear most frequently (optimization targets) Next steps: Profile actual GPU time per effect to validate bottlenecks. --- tools/seq_compiler.cc | 182 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 181 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/seq_compiler.cc b/tools/seq_compiler.cc index f9409de..87d6222 100644 --- a/tools/seq_compiler.cc +++ b/tools/seq_compiler.cc @@ -3,8 +3,11 @@ // Converts a text-based timeline description into C++ code. #include +#include #include +#include #include +#include #include #include #include @@ -46,6 +49,171 @@ int calculate_tick_interval(float max_time) { return 20; } +// Analyze effect stacking depth across the timeline +void analyze_effect_depth(const std::vector& sequences, + const std::string& demo_end_time, + float sample_rate = 10.0f) { + // Find max time for analysis + float max_time = demo_end_time.empty() ? 0.0f : std::stof(demo_end_time); + for (const auto& seq : sequences) { + float seq_start = std::stof(seq.start_time); + for (const auto& eff : seq.effects) { + float eff_end = seq_start + std::stof(eff.end); + max_time = std::max(max_time, eff_end); + } + } + + if (max_time <= 0.0f) { + std::cout << "\n=== Effect Depth Analysis ===\n"; + std::cout << "No effects found in timeline.\n"; + return; + } + + // Build list of all effects with absolute times + struct ActiveEffect { + std::string name; + float start_time; + float end_time; + int priority; + }; + std::vector all_effects; + + for (const auto& seq : sequences) { + float seq_start = std::stof(seq.start_time); + float seq_end = seq_start; + if (seq.end_time != "-1.0") { + seq_end = seq_start + std::stof(seq.end_time); + } + + for (const auto& eff : seq.effects) { + float eff_start = seq_start + std::stof(eff.start); + float eff_end = seq_start + std::stof(eff.end); + + // Clamp effect end to sequence end if specified + if (seq.end_time != "-1.0") { + eff_end = std::min(eff_end, seq_end); + } + + all_effects.push_back( + {eff.class_name, eff_start, eff_end, std::stoi(eff.priority)}); + } + } + + // Sample timeline at regular intervals + const float dt = 1.0f / sample_rate; + int max_depth = 0; + float max_depth_time = 0.0f; + std::vector depth_histogram(21, 0); // Track depths 0-20+ + + struct PeakInfo { + float time; + int depth; + std::vector effects; + }; + std::vector peaks; + + for (float t = 0.0f; t <= max_time; t += dt) { + int depth = 0; + std::vector active_effects; + + for (const auto& eff : all_effects) { + if (t >= eff.start_time && t < eff.end_time) { + depth++; + active_effects.push_back(eff.name); + } + } + + // Update max depth + if (depth > max_depth) { + max_depth = depth; + max_depth_time = t; + } + + // Record histogram + int hist_idx = std::min(depth, 20); + depth_histogram[hist_idx]++; + + // Record peaks (>5 effects) + if (depth > 5 && (peaks.empty() || t - peaks.back().time > 0.5f)) { + peaks.push_back({t, depth, active_effects}); + } + } + + // Print analysis report + std::cout << "\n=== Effect Depth Analysis ===\n"; + std::cout << "Timeline duration: " << max_time << "s\n"; + std::cout << "Total effects: " << all_effects.size() << "\n"; + std::cout << "Sample rate: " << sample_rate << " Hz (every " << dt << "s)\n"; + std::cout << "\n"; + + std::cout << "Max concurrent effects: " << max_depth << " at t=" << max_depth_time + << "s\n"; + std::cout << "\n"; + + // Print histogram + std::cout << "Effect Depth Distribution:\n"; + std::cout << "Depth | Count | Percentage | Bar\n"; + std::cout << "------|---------|------------|" + "-------------------------------------\n"; + + int total_samples = + std::accumulate(depth_histogram.begin(), depth_histogram.end(), 0); + + for (size_t i = 0; i < depth_histogram.size(); ++i) { + if (depth_histogram[i] == 0 && i > max_depth) + continue; + + float percentage = 100.0f * depth_histogram[i] / total_samples; + int bar_length = (int)(percentage / 2.0f); // Scale to ~50 chars max + + std::cout << std::setw(5) << (i < 20 ? std::to_string(i) : "20+") << " | " + << std::setw(7) << depth_histogram[i] << " | " << std::setw(9) + << std::fixed << std::setprecision(1) << percentage << "% | "; + + for (int j = 0; j < bar_length; ++j) { + std::cout << "█"; + } + std::cout << "\n"; + } + + // Print bottleneck warnings + if (max_depth > 5) { + std::cout << "\n⚠ WARNING: Performance bottlenecks detected!\n"; + std::cout << "Found " << peaks.size() << " time periods with >5 effects:\n\n"; + + int peak_count = 0; + for (const auto& peak : peaks) { + if (peak_count >= 10) + break; // Limit output + + std::cout << " t=" << std::fixed << std::setprecision(2) << peak.time + << "s: " << peak.depth << " effects [ "; + + // Show first 5 effects + for (size_t i = 0; i < std::min(size_t(5), peak.effects.size()); ++i) { + std::cout << peak.effects[i]; + if (i < peak.effects.size() - 1) + std::cout << ", "; + } + if (peak.effects.size() > 5) { + std::cout << " +" << (peak.effects.size() - 5) << " more"; + } + std::cout << " ]\n"; + + peak_count++; + } + + if (peaks.size() > 10) { + std::cout << " ... and " << (peaks.size() - 10) << " more peaks\n"; + } + } else { + std::cout << "\n✓ No significant bottlenecks detected (max depth: " + << max_depth << " <= 5)\n"; + } + + std::cout << "\n"; +} + // Generate ASCII Gantt chart for timeline visualization void generate_gantt_chart(const std::string& output_file, const std::vector& sequences, @@ -452,16 +620,20 @@ int main(int argc, char* argv[]) { if (argc < 2) { std::cerr << "Usage: " << argv[0] << " [output.cc] [--gantt=] " - "[--gantt-html=]\n"; + "[--gantt-html=] [--analyze]\n"; std::cerr << "Examples:\n"; std::cerr << " " << argv[0] << " assets/demo.seq src/generated/timeline.cc\n"; std::cerr << " " << argv[0] << " assets/demo.seq --gantt=timeline.txt\n"; std::cerr << " " << argv[0] << " assets/demo.seq --gantt-html=timeline.html\n"; + std::cerr << " " << argv[0] << " assets/demo.seq --analyze\n"; std::cerr << " " << argv[0] << " assets/demo.seq timeline.cc --gantt=timeline.txt " "--gantt-html=timeline.html\n"; + std::cerr << "\nOptions:\n"; + std::cerr << " --analyze Analyze effect stacking depth and " + "identify bottlenecks\n"; std::cerr << "\nIf output.cc is omitted, only validation and Gantt " "generation are performed.\n"; return 1; @@ -470,6 +642,7 @@ int main(int argc, char* argv[]) { std::string output_cc = ""; std::string gantt_output = ""; std::string gantt_html_output = ""; + bool analyze_depth = false; // Parse command line arguments for (int i = 2; i < argc; ++i) { @@ -478,6 +651,8 @@ int main(int argc, char* argv[]) { gantt_output = arg.substr(8); } else if (arg.rfind("--gantt-html=", 0) == 0) { gantt_html_output = arg.substr(13); + } else if (arg == "--analyze") { + analyze_depth = true; } else if (output_cc.empty() && arg[0] != '-') { output_cc = arg; } @@ -738,5 +913,10 @@ int main(int argc, char* argv[]) { generate_gantt_html(gantt_html_output, sequences, bpm, demo_end_time); } + // Analyze effect stacking depth if requested + if (analyze_depth) { + analyze_effect_depth(sequences, demo_end_time); + } + return 0; } -- cgit v1.2.3