2025-08-06 22:19:13 +03:00
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
|
|
import 'package:args/args.dart';
|
2025-08-07 14:18:16 +03:00
|
|
|
import 'package:benchmark_di/scenarios/universal_binding_mode.dart';
|
|
|
|
|
import 'package:benchmark_di/scenarios/universal_scenario.dart';
|
2025-08-06 22:19:13 +03:00
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// Enum describing all supported Universal DI benchmark types.
|
2025-08-06 22:19:13 +03:00
|
|
|
enum UniversalBenchmark {
|
2026-04-22 20:33:07 +03:00
|
|
|
/// Simple singleton registration benchmark (eager, where supported)
|
2025-08-06 22:19:13 +03:00
|
|
|
registerSingleton,
|
2025-08-13 15:38:44 +03:00
|
|
|
|
2026-04-22 20:33:07 +03:00
|
|
|
/// Simple lazy singleton registration benchmark
|
|
|
|
|
registerLazySingleton,
|
|
|
|
|
|
|
|
|
|
/// Chain of eager singleton dependencies
|
2025-08-06 22:19:13 +03:00
|
|
|
chainSingleton,
|
2025-08-13 15:38:44 +03:00
|
|
|
|
2026-04-22 20:33:07 +03:00
|
|
|
/// Chain of lazy singleton dependencies
|
|
|
|
|
chainLazySingleton,
|
|
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// Chain using factories
|
2025-08-06 22:19:13 +03:00
|
|
|
chainFactory,
|
2025-08-13 15:38:44 +03:00
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// Async chain resolution
|
2025-08-06 22:19:13 +03:00
|
|
|
chainAsync,
|
2025-08-13 15:38:44 +03:00
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// Named registration benchmark
|
2025-08-06 22:19:13 +03:00
|
|
|
named,
|
2025-08-13 15:38:44 +03:00
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// Override/child-scope benchmark
|
2025-08-06 22:19:13 +03:00
|
|
|
override,
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 20:33:07 +03:00
|
|
|
enum ResolvePhase {
|
|
|
|
|
firstResolve,
|
|
|
|
|
steadyStateResolve,
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// Maps [UniversalBenchmark] to the scenario enum for DI chains.
|
2025-08-06 22:19:13 +03:00
|
|
|
UniversalScenario toScenario(UniversalBenchmark b) {
|
|
|
|
|
switch (b) {
|
|
|
|
|
case UniversalBenchmark.registerSingleton:
|
2026-04-22 20:33:07 +03:00
|
|
|
case UniversalBenchmark.registerLazySingleton:
|
2025-08-06 22:19:13 +03:00
|
|
|
return UniversalScenario.register;
|
|
|
|
|
case UniversalBenchmark.chainSingleton:
|
2026-04-22 20:33:07 +03:00
|
|
|
case UniversalBenchmark.chainLazySingleton:
|
2025-08-06 22:19:13 +03:00
|
|
|
return UniversalScenario.chain;
|
|
|
|
|
case UniversalBenchmark.chainFactory:
|
|
|
|
|
return UniversalScenario.chain;
|
|
|
|
|
case UniversalBenchmark.chainAsync:
|
|
|
|
|
return UniversalScenario.asyncChain;
|
|
|
|
|
case UniversalBenchmark.named:
|
|
|
|
|
return UniversalScenario.named;
|
|
|
|
|
case UniversalBenchmark.override:
|
|
|
|
|
return UniversalScenario.override;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 20:33:07 +03:00
|
|
|
/// Maps benchmark to registration mode (singleton/lazySingleton/factory/async).
|
2025-08-06 22:19:13 +03:00
|
|
|
UniversalBindingMode toMode(UniversalBenchmark b) {
|
|
|
|
|
switch (b) {
|
|
|
|
|
case UniversalBenchmark.registerSingleton:
|
|
|
|
|
case UniversalBenchmark.chainSingleton:
|
2026-04-22 20:33:07 +03:00
|
|
|
case UniversalBenchmark.named:
|
|
|
|
|
case UniversalBenchmark.override:
|
2025-08-06 22:19:13 +03:00
|
|
|
return UniversalBindingMode.singletonStrategy;
|
2026-04-22 20:33:07 +03:00
|
|
|
case UniversalBenchmark.registerLazySingleton:
|
|
|
|
|
case UniversalBenchmark.chainLazySingleton:
|
|
|
|
|
return UniversalBindingMode.lazySingletonStrategy;
|
2025-08-06 22:19:13 +03:00
|
|
|
case UniversalBenchmark.chainFactory:
|
|
|
|
|
return UniversalBindingMode.factoryStrategy;
|
|
|
|
|
case UniversalBenchmark.chainAsync:
|
|
|
|
|
return UniversalBindingMode.asyncStrategy;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// Utility to parse a string into its corresponding enum value [T].
|
2025-08-06 22:19:13 +03:00
|
|
|
T parseEnum<T>(String value, List<T> values, T defaultValue) {
|
|
|
|
|
return values.firstWhere(
|
|
|
|
|
(v) => v.toString().split('.').last.toLowerCase() == value.toLowerCase(),
|
|
|
|
|
orElse: () => defaultValue,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// Parses comma-separated integer list from [s].
|
2025-08-13 15:38:44 +03:00
|
|
|
List<int> parseIntList(String s) => s
|
|
|
|
|
.split(',')
|
|
|
|
|
.map((e) => int.tryParse(e.trim()) ?? 0)
|
|
|
|
|
.where((x) => x > 0)
|
|
|
|
|
.toList();
|
2025-08-06 22:19:13 +03:00
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// CLI config describing what and how to benchmark.
|
2025-08-06 22:19:13 +03:00
|
|
|
class BenchmarkCliConfig {
|
2025-08-06 23:15:28 +03:00
|
|
|
/// Benchmarks enabled to run (scenarios).
|
2025-08-06 22:19:13 +03:00
|
|
|
final List<UniversalBenchmark> benchesToRun;
|
2025-08-13 15:38:44 +03:00
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// List of chain counts (parallel, per test).
|
2025-08-06 22:19:13 +03:00
|
|
|
final List<int> chainCounts;
|
2025-08-13 15:38:44 +03:00
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// List of nesting depths (max chain length, per test).
|
2025-08-06 22:19:13 +03:00
|
|
|
final List<int> nestDepths;
|
2025-08-13 15:38:44 +03:00
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// How many times to repeat each trial.
|
2025-08-06 22:19:13 +03:00
|
|
|
final int repeats;
|
2025-08-13 15:38:44 +03:00
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// How many times to warm-up before measuring.
|
2025-08-06 22:19:13 +03:00
|
|
|
final int warmups;
|
2025-08-13 15:38:44 +03:00
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// Output report format.
|
2025-08-06 22:19:13 +03:00
|
|
|
final String format;
|
2025-08-13 15:38:44 +03:00
|
|
|
|
2025-08-07 09:15:26 +03:00
|
|
|
/// Name of DI implementation ("cherrypick" or "getit")
|
|
|
|
|
final String di;
|
2026-04-22 20:33:07 +03:00
|
|
|
|
|
|
|
|
/// Which resolve phase(s) to measure.
|
|
|
|
|
final List<ResolvePhase> phases;
|
|
|
|
|
|
2025-08-06 22:19:13 +03:00
|
|
|
BenchmarkCliConfig({
|
|
|
|
|
required this.benchesToRun,
|
|
|
|
|
required this.chainCounts,
|
|
|
|
|
required this.nestDepths,
|
|
|
|
|
required this.repeats,
|
|
|
|
|
required this.warmups,
|
|
|
|
|
required this.format,
|
2025-08-07 09:15:26 +03:00
|
|
|
required this.di,
|
2026-04-22 20:33:07 +03:00
|
|
|
required this.phases,
|
2025-08-06 22:19:13 +03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-06 23:15:28 +03:00
|
|
|
/// Parses CLI arguments [args] into a [BenchmarkCliConfig].
|
|
|
|
|
/// Supports --benchmark, --chainCount, --nestingDepth, etc.
|
2025-08-06 22:19:13 +03:00
|
|
|
BenchmarkCliConfig parseBenchmarkCli(List<String> args) {
|
|
|
|
|
final parser = ArgParser()
|
|
|
|
|
..addOption('benchmark', abbr: 'b', defaultsTo: 'chainSingleton')
|
|
|
|
|
..addOption('chainCount', abbr: 'c', defaultsTo: '10')
|
|
|
|
|
..addOption('nestingDepth', abbr: 'd', defaultsTo: '5')
|
|
|
|
|
..addOption('repeat', abbr: 'r', defaultsTo: '2')
|
|
|
|
|
..addOption('warmup', abbr: 'w', defaultsTo: '1')
|
|
|
|
|
..addOption('format', abbr: 'f', defaultsTo: 'pretty')
|
2026-04-22 20:33:07 +03:00
|
|
|
..addOption('resolvePhase',
|
|
|
|
|
defaultsTo: 'all', help: 'Resolve phase: first, steady, or all')
|
2025-08-13 15:38:44 +03:00
|
|
|
..addOption('di',
|
|
|
|
|
defaultsTo: 'cherrypick',
|
|
|
|
|
help: 'DI implementation: cherrypick, getit or riverpod')
|
2025-08-06 22:19:13 +03:00
|
|
|
..addFlag('help', abbr: 'h', negatable: false, help: 'Show help');
|
|
|
|
|
final result = parser.parse(args);
|
|
|
|
|
if (result['help'] == true) {
|
|
|
|
|
print(parser.usage);
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|
2026-04-22 20:33:07 +03:00
|
|
|
final benchNameInput = result['benchmark'] as String;
|
|
|
|
|
final isAll = benchNameInput.trim() == 'all';
|
2025-08-06 22:19:13 +03:00
|
|
|
final allBenches = UniversalBenchmark.values;
|
2026-04-22 20:33:07 +03:00
|
|
|
|
|
|
|
|
String normalizeBenchName(String name) {
|
|
|
|
|
final n = name.trim().toLowerCase();
|
|
|
|
|
return switch (n) {
|
|
|
|
|
'register' || 'registersingleton' || 'registereager' => 'registerSingleton',
|
|
|
|
|
'registerlazy' || 'registerlazysingleton' || 'registerlazysingle' => 'registerLazySingleton',
|
|
|
|
|
'chain' || 'chainsingleton' || 'chaineager' => 'chainSingleton',
|
|
|
|
|
'chainlazy' || 'chainlazysingleton' || 'lazysingleton' => 'chainLazySingleton',
|
|
|
|
|
'chainfactory' || 'factory' => 'chainFactory',
|
|
|
|
|
'async' || 'asyncchain' || 'chainasync' => 'chainAsync',
|
|
|
|
|
'named' => 'named',
|
|
|
|
|
'override' => 'override',
|
|
|
|
|
_ => n,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-06 22:19:13 +03:00
|
|
|
final benchesToRun = isAll
|
|
|
|
|
? allBenches
|
2026-04-22 20:33:07 +03:00
|
|
|
: benchNameInput
|
|
|
|
|
.split(',')
|
|
|
|
|
.map((n) => parseEnum(normalizeBenchName(n), allBenches,
|
|
|
|
|
UniversalBenchmark.chainSingleton))
|
|
|
|
|
.toSet()
|
|
|
|
|
.toList();
|
|
|
|
|
final phaseName = (result['resolvePhase'] as String).toLowerCase();
|
|
|
|
|
final phases = switch (phaseName) {
|
|
|
|
|
'first' => [ResolvePhase.firstResolve],
|
|
|
|
|
'steady' => [ResolvePhase.steadyStateResolve],
|
|
|
|
|
_ => ResolvePhase.values,
|
|
|
|
|
};
|
2025-08-06 22:19:13 +03:00
|
|
|
return BenchmarkCliConfig(
|
|
|
|
|
benchesToRun: benchesToRun,
|
|
|
|
|
chainCounts: parseIntList(result['chainCount'] as String),
|
|
|
|
|
nestDepths: parseIntList(result['nestingDepth'] as String),
|
|
|
|
|
repeats: int.tryParse(result['repeat'] as String? ?? "") ?? 2,
|
|
|
|
|
warmups: int.tryParse(result['warmup'] as String? ?? "") ?? 1,
|
|
|
|
|
format: result['format'] as String,
|
2025-08-07 09:15:26 +03:00
|
|
|
di: result['di'] as String? ?? 'cherrypick',
|
2026-04-22 20:33:07 +03:00
|
|
|
phases: phases,
|
2025-08-06 22:19:13 +03:00
|
|
|
);
|
2025-08-13 15:38:44 +03:00
|
|
|
}
|