| // 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<EventLoopLatencyStats> measureEventLoopLatency( |
| Duration tickDuration, int numberOfTicks) { |
| final completer = Completer<EventLoopLatencyStats>(); |
| |
| 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); |
| } |
| } |