115 lines
3.7 KiB
Dart
115 lines
3.7 KiB
Dart
|
|
import 'package:iperf3gui/domain/models/iperf3_event.dart';
|
||
|
|
|
||
|
|
/// The accumulated state of one iperf3 run.
|
||
|
|
///
|
||
|
|
/// Fed the event stream one report at a time, this is the answer to "what
|
||
|
|
/// happened during this test" — the interval series, the end totals, any
|
||
|
|
/// errors, and the statistics derived from them.
|
||
|
|
///
|
||
|
|
/// It lives in the domain layer on purpose: it is what has to be *correct*,
|
||
|
|
/// independent of how anything chooses to draw it. A different UI, a headless
|
||
|
|
/// run or a future export path all consume the same object, and none of them
|
||
|
|
/// re-derive a mean or re-decide whether a warm-up interval counts.
|
||
|
|
class Iperf3Session {
|
||
|
|
Iperf3Start? _start;
|
||
|
|
|
||
|
|
/// Connection details, once the run has reported them.
|
||
|
|
Iperf3Start? get start => _start;
|
||
|
|
|
||
|
|
final List<Iperf3Interval> _intervals = [];
|
||
|
|
|
||
|
|
/// Throughput samples in arrival order, warm-up intervals excluded.
|
||
|
|
List<Iperf3Interval> get intervals => List.unmodifiable(_intervals);
|
||
|
|
|
||
|
|
Iperf3End? _summary;
|
||
|
|
|
||
|
|
/// Sent/received totals, once the run has finished.
|
||
|
|
Iperf3End? get summary => _summary;
|
||
|
|
|
||
|
|
final List<String> _errors = [];
|
||
|
|
|
||
|
|
/// Errors reported during the run, in order.
|
||
|
|
List<String> get errors => List.unmodifiable(_errors);
|
||
|
|
|
||
|
|
bool _stopped = false;
|
||
|
|
|
||
|
|
/// Whether the run ended because it was asked to stop.
|
||
|
|
bool get stopped => _stopped;
|
||
|
|
|
||
|
|
/// Whether iperf3 reported any error.
|
||
|
|
bool get hasError => _errors.isNotEmpty;
|
||
|
|
|
||
|
|
/// Whether anything at all has been recorded.
|
||
|
|
bool get isEmpty =>
|
||
|
|
_start == null && _intervals.isEmpty && _summary == null && !hasError;
|
||
|
|
|
||
|
|
/// How many throughput samples have been recorded.
|
||
|
|
int get sampleCount => _intervals.length;
|
||
|
|
|
||
|
|
/// Throughput of the most recent sample, or null before the first one.
|
||
|
|
double? get latestBitsPerSecond =>
|
||
|
|
_intervals.isEmpty ? null : _intervals.last.bitsPerSecond;
|
||
|
|
|
||
|
|
/// Mean throughput across the samples, or null before the first one.
|
||
|
|
///
|
||
|
|
/// This is the mean of the per-interval rates, which is not identical to the
|
||
|
|
/// run's overall rate when intervals differ in length — prefer
|
||
|
|
/// [summary]'s totals for the headline figure once it is available.
|
||
|
|
double? get meanBitsPerSecond {
|
||
|
|
if (_intervals.isEmpty) return null;
|
||
|
|
final total = _intervals.fold<double>(0, (sum, i) => sum + i.bitsPerSecond);
|
||
|
|
return total / _intervals.length;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Highest sampled throughput, or null before the first sample.
|
||
|
|
double? get peakBitsPerSecond {
|
||
|
|
if (_intervals.isEmpty) return null;
|
||
|
|
return _intervals
|
||
|
|
.map((i) => i.bitsPerSecond)
|
||
|
|
.reduce((a, b) => a > b ? a : b);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Total bytes across the recorded samples.
|
||
|
|
int get sampledBytes => _intervals.fold<int>(0, (sum, i) => sum + i.bytes);
|
||
|
|
|
||
|
|
/// Total retransmits across the samples, or null if none were reported.
|
||
|
|
///
|
||
|
|
/// UDP tests report no retransmit counts, so null means "not measured"
|
||
|
|
/// rather than zero.
|
||
|
|
int? get totalRetransmits {
|
||
|
|
final reported =
|
||
|
|
_intervals.map((i) => i.retransmits).whereType<int>().toList();
|
||
|
|
if (reported.isEmpty) return null;
|
||
|
|
return reported.fold<int>(0, (sum, r) => sum + r);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Record one report.
|
||
|
|
void add(Iperf3Event event) {
|
||
|
|
switch (event) {
|
||
|
|
case Iperf3Start e:
|
||
|
|
_start = e;
|
||
|
|
case Iperf3Interval e:
|
||
|
|
// Warm-up intervals (-O) are marked omitted by iperf3 and must not
|
||
|
|
// drag the statistics down.
|
||
|
|
if (!e.omitted) _intervals.add(e);
|
||
|
|
case Iperf3End e:
|
||
|
|
_summary = e;
|
||
|
|
case Iperf3Error e:
|
||
|
|
_errors.add(e.message);
|
||
|
|
case Iperf3Stopped _:
|
||
|
|
_stopped = true;
|
||
|
|
case Iperf3LogLine _:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Forget everything, ready for another run.
|
||
|
|
void clear() {
|
||
|
|
_start = null;
|
||
|
|
_intervals.clear();
|
||
|
|
_summary = null;
|
||
|
|
_errors.clear();
|
||
|
|
_stopped = false;
|
||
|
|
}
|
||
|
|
}
|