import 'dart:math'; import 'package:benchmark_di/cli/report/markdown_report.dart'; import 'package:benchmark_di/di_adapters/yx_scope_adapter.dart'; import 'package:benchmark_di/di_adapters/yx_scope_universal_container.dart'; import 'package:benchmark_di/scenarios/universal_scenario.dart'; import 'package:cherrypick/cherrypick.dart'; import 'package:get_it/get_it.dart'; import 'package:riverpod/riverpod.dart' as rp; import 'report/pretty_report.dart'; import 'report/csv_report.dart'; import 'report/json_report.dart'; import 'parser.dart'; import 'runner.dart'; import 'package:benchmark_di/benchmarks/universal_chain_benchmark.dart'; import 'package:benchmark_di/benchmarks/universal_chain_async_benchmark.dart'; import 'package:benchmark_di/di_adapters/cherrypick_adapter.dart'; import 'package:benchmark_di/di_adapters/get_it_adapter.dart'; import 'package:benchmark_di/di_adapters/riverpod_adapter.dart'; import 'package:benchmark_di/di_adapters/kiwi_adapter.dart'; import 'package:kiwi/kiwi.dart'; /// Command-line interface (CLI) runner for benchmarks. /// /// Parses CLI arguments, orchestrates benchmarks for different /// scenarios and configurations, collects results, and generates reports /// in the desired output format. class BenchmarkCliRunner { /// Runs benchmarks based on CLI [args], configuring different test scenarios. Future run(List args) async { final config = parseBenchmarkCli(args); final results = >[]; // DI implementations that do not support async scenarios const asyncUnsupported = {'kiwi', 'yx_scope'}; for (final phase in config.phases) { for (final bench in config.benchesToRun) { final scenario = toScenario(bench); final mode = toMode(bench); if (asyncUnsupported.contains(config.di) && scenario == UniversalScenario.asyncChain) { continue; // Skip async benchmarks for DI that does not support them } for (final c in config.chainCounts) { for (final d in config.nestDepths) { BenchmarkResult benchResult; if (config.di == 'getit') { final di = GetItAdapter(); if (scenario == UniversalScenario.asyncChain) { final benchAsync = UniversalChainAsyncBenchmark( di, chainCount: c, nestingDepth: d, mode: mode, ); benchResult = await BenchmarkRunner.runAsync( benchmark: benchAsync, warmups: config.warmups, repeats: config.repeats, phase: phase, ); } else { final benchSync = UniversalChainBenchmark( di, chainCount: c, nestingDepth: d, mode: mode, scenario: scenario, ); benchResult = await BenchmarkRunner.runSync( benchmark: benchSync, warmups: config.warmups, repeats: config.repeats, phase: phase, ); } } else if (config.di == 'kiwi') { final di = KiwiAdapter(); if (scenario == UniversalScenario.asyncChain) { final benchAsync = UniversalChainAsyncBenchmark( di, chainCount: c, nestingDepth: d, mode: mode, ); benchResult = await BenchmarkRunner.runAsync( benchmark: benchAsync, warmups: config.warmups, repeats: config.repeats, phase: phase, ); } else { final benchSync = UniversalChainBenchmark( di, chainCount: c, nestingDepth: d, mode: mode, scenario: scenario, ); benchResult = await BenchmarkRunner.runSync( benchmark: benchSync, warmups: config.warmups, repeats: config.repeats, phase: phase, ); } } else if (config.di == 'riverpod') { final di = RiverpodAdapter(); if (scenario == UniversalScenario.asyncChain) { final benchAsync = UniversalChainAsyncBenchmark< Map>>( di, chainCount: c, nestingDepth: d, mode: mode, ); benchResult = await BenchmarkRunner.runAsync( benchmark: benchAsync, warmups: config.warmups, repeats: config.repeats, phase: phase, ); } else { final benchSync = UniversalChainBenchmark< Map>>( di, chainCount: c, nestingDepth: d, mode: mode, scenario: scenario, ); benchResult = await BenchmarkRunner.runSync( benchmark: benchSync, warmups: config.warmups, repeats: config.repeats, phase: phase, ); } } else if (config.di == 'yx_scope') { final di = YxScopeAdapter(); if (scenario == UniversalScenario.asyncChain) { final benchAsync = UniversalChainAsyncBenchmark( di, chainCount: c, nestingDepth: d, mode: mode, ); benchResult = await BenchmarkRunner.runAsync( benchmark: benchAsync, warmups: config.warmups, repeats: config.repeats, phase: phase, ); } else { final benchSync = UniversalChainBenchmark( di, chainCount: c, nestingDepth: d, mode: mode, scenario: scenario, ); benchResult = await BenchmarkRunner.runSync( benchmark: benchSync, warmups: config.warmups, repeats: config.repeats, phase: phase, ); } } else { final di = CherrypickDIAdapter(); if (scenario == UniversalScenario.asyncChain) { final benchAsync = UniversalChainAsyncBenchmark( di, chainCount: c, nestingDepth: d, mode: mode, ); benchResult = await BenchmarkRunner.runAsync( benchmark: benchAsync, warmups: config.warmups, repeats: config.repeats, phase: phase, ); } else { final benchSync = UniversalChainBenchmark( di, chainCount: c, nestingDepth: d, mode: mode, scenario: scenario, ); benchResult = await BenchmarkRunner.runSync( benchmark: benchSync, warmups: config.warmups, repeats: config.repeats, phase: phase, ); } } final timings = benchResult.timings; if (timings.isEmpty) continue; // skip failed scenarios timings.sort(); final count = timings.length; final mean = timings.reduce((a, b) => a + b) / count; final median = count.isOdd ? timings[count ~/ 2] : (timings[count ~/ 2 - 1] + timings[count ~/ 2]) / 2; final minVal = timings.first; final maxVal = timings.last; final stddev = sqrt(timings .map((x) => pow(x - mean, 2)) .reduce((a, b) => a + b) / count); results.add({ 'benchmark': 'Universal_$bench', 'phase': phase.name, 'chainCount': c, 'nestingDepth': d, 'mean_us': mean.toStringAsFixed(2), 'median_us': median.toStringAsFixed(2), 'stddev_us': stddev.toStringAsFixed(2), 'min_us': minVal.toStringAsFixed(2), 'max_us': maxVal.toStringAsFixed(2), 'trials': timings.length, 'timings_us': timings.map((t) => t.toStringAsFixed(2)).toList(), 'memory_diff_kb': benchResult.memoryDiffKb, 'delta_peak_kb': benchResult.deltaPeakKb, 'peak_rss_kb': benchResult.peakRssKb, }); } } } } final reportGenerators = { 'pretty': PrettyReport(), 'csv': CsvReport(), 'json': JsonReport(), 'markdown': MarkdownReport(), }; print(reportGenerators[config.format]?.render(results) ?? PrettyReport().render(results)); } }