// Headless entry point: runs one iperf3 test with no widgets. // // This exists to keep the core honest. If anything in the run path ever starts // depending on the UI, this stops compiling or stops working — a claim about // separation that is checked rather than asserted. // // It is also the low-noise way to measure. The UI competes with the test for // CPU (a gigabit run can put the phone near CPU-bound), so figures taken here // are cleaner than figures taken with a transcript repainting alongside. // // flutter run -t lib/main_headless.dart \ // --dart-define=IPERF_SERVER=192.168.2.182 \ // --dart-define=IPERF_STREAMS=4 // // Nothing below imports lib/ui, and test/architecture_test.dart enforces that. import 'package:flutter/widgets.dart' show WidgetsFlutterBinding; import 'package:flutter/foundation.dart' show debugPrint; import 'package:iperf3gui/data/services/iperf3_service.dart'; import 'package:iperf3gui/domain/models/iperf3_event.dart'; import 'package:iperf3gui/domain/models/iperf3_session.dart'; import 'package:iperf3gui/domain/models/test_configuration.dart'; import 'package:iperf3gui/domain/services/iperf3_runner.dart'; /// Test parameters, supplied at build time with --dart-define. const String _server = String.fromEnvironment('IPERF_SERVER', defaultValue: 'speedtest.uztelecom.uz'); const int _port = int.fromEnvironment('IPERF_PORT', defaultValue: 5201); const int _seconds = int.fromEnvironment('IPERF_SECONDS', defaultValue: 10); const int _streams = int.fromEnvironment('IPERF_STREAMS', defaultValue: 1); Future main() async { // Required for the MethodChannel. Notably NOT runApp() — there is no widget // tree, no render surface and no Flutter UI thread work in this process. WidgetsFlutterBinding.ensureInitialized(); const config = TestConfiguration( server: _server, port: _port, duration: Duration(seconds: _seconds), parallelStreams: _streams, ); final Iperf3Runner runner = Iperf3Service(); final session = Iperf3Session(); final subscription = runner.events.listen((event) { session.add(event); if (event is Iperf3Interval && !event.omitted) { _out('interval ${event.start.toStringAsFixed(2)}-' '${event.end.toStringAsFixed(2)}s ' '${_mbits(event.bitsPerSecond)} Mbit/s' '${event.retransmits != null ? ' retr=${event.retransmits}' : ''}'); } else if (event is Iperf3Error) { _out('error: ${event.message}'); } }); _out('running iperf3 -c ${config.server} -p ${config.port} ' '-t ${config.duration.inSeconds} -P ${config.parallelStreams}'); final result = await runner.run(config); await subscription.cancel(); await runner.dispose(); _report(result.exitCode, session); } void _report(int exitCode, Iperf3Session session) { _out('--- result ---'); _out('exit code: $exitCode'); _out('samples: ${session.sampleCount}'); final mean = session.meanBitsPerSecond; final peak = session.peakBitsPerSecond; if (mean != null) _out('mean: ${_mbits(mean)} Mbit/s'); if (peak != null) _out('peak: ${_mbits(peak)} Mbit/s'); final retr = session.totalRetransmits; _out('retransmits: ${retr ?? 'not measured'}'); final sent = session.summary?.sent; final received = session.summary?.received; if (sent != null) { _out('sent: ${_mbits(sent.bitsPerSecond)} Mbit/s ' 'over ${sent.seconds.toStringAsFixed(2)}s'); } if (received != null) { _out('received: ${_mbits(received.bitsPerSecond)} Mbit/s ' 'over ${received.seconds.toStringAsFixed(2)}s'); } final cpu = session.summary?.hostCpuPercent; if (cpu != null) _out('local cpu: ${cpu.toStringAsFixed(1)}%'); if (session.stopped) _out('ended early: stopped'); for (final error in session.errors) { _out('error: $error'); } } String _mbits(double bitsPerSecond) => (bitsPerSecond / 1e6).toStringAsFixed(1); void _out(String line) => debugPrint('[iperf3-headless] $line');