| // 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. | 
 |  | 
 | 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, | 
 |     ); | 
 |   } | 
 | } |