// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. // @dart=2.9 import 'dart:async'; import 'dart:io'; import 'dart:math' as math; import 'dart:typed_data'; /// Measures event loop responsiveness. /// /// Schedules new timer events, [tickDuration] in the future, and measures how /// long it takes for these events to actually arrive. /// /// Runs [numberOfTicks] times before completing with [EventLoopLatencyStats]. Future measureEventLoopLatency( Duration tickDuration, int numberOfTicks, ) { final completer = Completer(); final tickDurationInUs = tickDuration.inMicroseconds; final buffer = _TickLatencies(numberOfTicks); final sw = Stopwatch()..start(); int lastTimestamp = 0; void trigger() { final int currentTimestamp = sw.elapsedMicroseconds; // Every tick we missed to schedule we'll add with difference to when we // would've scheduled it and when we became responsive again. bool done = false; while (!done && lastTimestamp < (currentTimestamp - tickDurationInUs)) { done = !buffer.add(currentTimestamp - lastTimestamp - tickDurationInUs); lastTimestamp += tickDurationInUs; } if (!done) { lastTimestamp = currentTimestamp; Timer(tickDuration, trigger); } else { completer.complete(buffer.makeStats()); } } Timer(tickDuration, trigger); return completer.future; } /// Result of the event loop latency measurement. class EventLoopLatencyStats { /// Minimum latency between scheduling a tick and it's arrival (in ms). final double minLatency; /// Average latency between scheduling a tick and it's arrival (in ms). final double avgLatency; /// Maximum latency between scheduling a tick and it's arrival (in ms). final double maxLatency; /// The 50th percentile (median) (in ms). final double percentile50th; /// The 90th percentile (in ms). final double percentile90th; /// The 95th percentile (in ms). final double percentile95th; /// The 99th percentile (in ms). final double percentile99th; /// The maximum RSS of the process. final int maxRss; EventLoopLatencyStats( this.minLatency, this.avgLatency, this.maxLatency, this.percentile50th, this.percentile90th, this.percentile95th, this.percentile99th, this.maxRss, ); void report(String name) { print('$name.Min(RunTimeRaw): $minLatency ms.'); print('$name.Avg(RunTimeRaw): $avgLatency ms.'); print('$name.Percentile50(RunTimeRaw): $percentile50th ms.'); print('$name.Percentile90(RunTimeRaw): $percentile90th ms.'); print('$name.Percentile95(RunTimeRaw): $percentile95th ms.'); print('$name.Percentile99(RunTimeRaw): $percentile99th ms.'); print('$name.Max(RunTimeRaw): $maxLatency ms.'); print('$name.MaxRss(MemoryUse): $maxRss'); } } /// Accumulates tick latencies and makes statistics for it. class _TickLatencies { final Uint64List _timestamps; int _index = 0; _TickLatencies(int numberOfTicks) : _timestamps = Uint64List(numberOfTicks); /// Returns `true` while the buffer has not been filled yet. bool add(int latencyInUs) { _timestamps[_index++] = latencyInUs; return _index < _timestamps.length; } EventLoopLatencyStats makeStats() { if (_index != _timestamps.length) { throw 'Buffer has not been fully filled yet.'; } _timestamps.sort(); final length = _timestamps.length; final double avg = _timestamps.fold(0, (int a, int b) => a + b) / length; final int min = _timestamps.fold(0x7fffffffffffffff, math.min); final int max = _timestamps.fold(0, math.max); final percentile50th = _timestamps[50 * length ~/ 100]; final percentile90th = _timestamps[90 * length ~/ 100]; final percentile95th = _timestamps[95 * length ~/ 100]; final percentile99th = _timestamps[99 * length ~/ 100]; return EventLoopLatencyStats( min / 1000, avg / 1000, max / 1000, percentile50th / 1000, percentile90th / 1000, percentile95th / 1000, percentile99th / 1000, ProcessInfo.maxRss, ); } }