| // Copyright (c) 2012, 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. | |
| library; | |
| import 'dart:async' | |
| show Future, Zone, ZoneDelegate, ZoneSpecification, runZoned; | |
| import 'metrics.dart'; | |
| /// Used to measure where time is spent in the compiler. | |
| /// | |
| /// This exposes [measure] and [measureIo], which wrap an action and associate | |
| /// the time spent during that action with this task. Nested measurementsccan be | |
| /// introduced by using [measureSubtask]. | |
| // TODO(sigmund): rename to MeasurableTask | |
| abstract class CompilerTask { | |
| final Measurer _measurer; | |
| final Stopwatch? _watch; | |
| final Map<String, GenericTask> _subtasks = {}; | |
| int _asyncCount = 0; | |
| // Each task has a fixed, lazily computed, ZoneSpecification and zoneValues | |
| // for [_measureZoned]. | |
| ZoneSpecification? _zoneSpecification; | |
| Map<Measurer, CompilerTask>? _zoneValues; | |
| CompilerTask(this._measurer) | |
| : _watch = _measurer.enableTaskMeasurements ? Stopwatch() : null; | |
| /// Whether measurement is disabled. The functions [measure] and [measureIo] | |
| /// only measure time if measurements are enabled. | |
| bool get _isDisabled => _watch == null; | |
| /// Name to use for reporting timing information. | |
| /// | |
| /// Subclasses should override this with a proper name, otherwise we use the | |
| /// runtime type of the task. | |
| String get name => "Unknown task '$runtimeType'"; | |
| bool get isRunning => _watch?.isRunning == true; | |
| int get timing { | |
| if (_isDisabled) return 0; | |
| int total = _watch!.elapsedMilliseconds; | |
| for (GenericTask subtask in _subtasks.values) { | |
| total += subtask.timing; | |
| } | |
| return total; | |
| } | |
| Duration get duration { | |
| if (_isDisabled) return Duration.zero; | |
| Duration total = _watch!.elapsed; | |
| for (GenericTask subtask in _subtasks.values) { | |
| total += subtask.duration; | |
| } | |
| return total; | |
| } | |
| /// Perform [action] and measure its runtime (including any asynchronous | |
| /// callbacks, such as, [Future.then], but excluding code measured by other | |
| /// tasks). | |
| T measure<T>(T Function() action) => | |
| _isDisabled ? action() : _measureZoned(action); | |
| /// Helper method that starts measuring with this [CompilerTask], that is, | |
| /// make this task the currently measured task. | |
| CompilerTask? _start() { | |
| if (_isDisabled) return null; | |
| CompilerTask? previous = _measurer._currentTask; | |
| _measurer._currentTask = this; | |
| if (previous != null) previous._watch!.stop(); | |
| // Regardless of whether [previous] is `null` we've returned from the | |
| // eventloop. | |
| _measurer.stopAsyncWallClock(); | |
| _watch!.start(); | |
| return previous; | |
| } | |
| /// Helper method that stops measuring with this [CompilerTask], that is, | |
| /// make [previous] the currently measured task. | |
| void _stop(CompilerTask? previous) { | |
| if (_isDisabled) return; | |
| _watch!.stop(); | |
| if (previous != null) { | |
| previous._watch!.start(); | |
| } else { | |
| // If there's no previous task, we're about to return control to the | |
| // event loop. Start counting that as waiting asynchronous I/O. | |
| _measurer.startAsyncWallClock(); | |
| } | |
| _measurer._currentTask = previous; | |
| } | |
| T _measureZoned<T>(T Function() action) { | |
| // Using zones, we're able to track asynchronous operations correctly, as | |
| // our zone will be asked to invoke `then` blocks. Then blocks (the closure | |
| // passed to runZoned, and other closures) are run via the `run` functions | |
| // below. | |
| assert(_watch != null); | |
| // The current zone is already measuring `this` task. | |
| if (Zone.current[_measurer] == this) return action(); | |
| return runZoned( | |
| action, | |
| zoneValues: _zoneValues ??= {_measurer: this}, | |
| zoneSpecification: _zoneSpecification ??= ZoneSpecification( | |
| run: _run, | |
| runUnary: _runUnary, | |
| runBinary: _runBinary, | |
| ), | |
| ); | |
| } | |
| /// Run [f] in [zone]. Running must be delegated to [parent] to ensure that | |
| /// various state is set up correctly (in particular that `Zone.current` | |
| /// has the right value). Since [_measureZoned] can be called recursively | |
| /// (synchronously), some of the measuring zones we create will be parents | |
| /// of other measuring zones, but we still need to call through the parent | |
| /// chain. Consequently, we use a zone value keyed by [_measurer] to see if | |
| /// we should measure or not when delegating. | |
| R _run<R>(Zone self, ZoneDelegate parent, Zone zone, R Function() f) { | |
| if (zone[_measurer] != this) return parent.run(zone, f); | |
| CompilerTask? previous = _start(); | |
| try { | |
| return parent.run(zone, f); | |
| } finally { | |
| _stop(previous); | |
| } | |
| } | |
| /// Same as [run] except that [f] takes one argument, [arg]. | |
| R _runUnary<R, T>( | |
| Zone self, | |
| ZoneDelegate parent, | |
| Zone zone, | |
| R Function(T arg) f, | |
| T arg, | |
| ) { | |
| if (zone[_measurer] != this) return parent.runUnary(zone, f, arg); | |
| CompilerTask? previous = _start(); | |
| try { | |
| return parent.runUnary(zone, f, arg); | |
| } finally { | |
| _stop(previous); | |
| } | |
| } | |
| /// Same as [run] except that [f] takes two arguments ([a1] and [a2]). | |
| R _runBinary<R, T1, T2>( | |
| Zone self, | |
| ZoneDelegate parent, | |
| Zone zone, | |
| R Function(T1 a1, T2 a2) f, | |
| T1 a1, | |
| T2 a2, | |
| ) { | |
| if (zone[_measurer] != this) return parent.runBinary(zone, f, a1, a2); | |
| CompilerTask? previous = _start(); | |
| try { | |
| return parent.runBinary(zone, f, a1, a2); | |
| } finally { | |
| _stop(previous); | |
| } | |
| } | |
| /// Asynchronous version of [measure]. Use this when action returns a future | |
| /// that's truly asynchronous, such I/O. Only one task can use this method | |
| /// concurrently. | |
| /// | |
| /// Note: we assume that this method is used only by the compiler input | |
| /// provider, but it could be used by other tasks as long as the input | |
| /// provider will not be called by those tasks. | |
| Future<T> measureIo<T>(Future<T> Function() action) { | |
| if (_isDisabled) return action(); | |
| if (_measurer._currentAsyncTask == null) { | |
| _measurer._currentAsyncTask = this; | |
| } else if (_measurer._currentAsyncTask != this) { | |
| throw "Cannot track async task '$name' because" | |
| " '${_measurer._currentAsyncTask?.name}' is already being tracked."; | |
| } | |
| _asyncCount++; | |
| return measure(action).whenComplete(() { | |
| _asyncCount--; | |
| if (_asyncCount == 0) _measurer._currentAsyncTask = null; | |
| }); | |
| } | |
| /// Measure the time spent in [action] (if in verbose mode) and accumulate it | |
| /// under a subtask with the given name. | |
| T measureSubtask<T>(String name, T Function() action) { | |
| if (_isDisabled) return action(); | |
| // Use a nested CompilerTask for the measurement to ensure nested [measure] | |
| // calls work correctly. The subtasks will never themselves have nested | |
| // subtasks because they are not accessible outside. | |
| GenericTask subtask = _subtasks[name] ??= GenericTask(name, _measurer); | |
| return subtask.measure(action); | |
| } | |
| /// Asynchronous version of [measureSubtask]. Use this when action returns a | |
| /// future that's truly asynchronous, such I/O. Only one task can use this | |
| /// concurrently. | |
| /// | |
| /// Note: we assume that this method is used only by the compiler input | |
| /// provider, but it could be used by other tasks as long as the input | |
| /// provider will not be called by those tasks. | |
| Future<T> measureIoSubtask<T>(String name, Future<T> Function() action) { | |
| if (_isDisabled) return action(); | |
| // Use a nested CompilerTask for the measurement to ensure nested [measure] | |
| // calls work correctly. The subtasks will never themselves have nested | |
| // subtasks because they are not accessible outside. | |
| GenericTask subtask = _subtasks[name] ??= GenericTask(name, _measurer); | |
| return subtask.measureIo(action); | |
| } | |
| Iterable<String> get subtasks => _subtasks.keys; | |
| int getSubtaskTime(String subtask) => _subtasks[subtask]!.timing; | |
| bool getSubtaskIsRunning(String subtask) => _subtasks[subtask]!.isRunning; | |
| /// Returns the metrics for this task. | |
| Metrics get metrics => Metrics.none(); | |
| } | |
| class GenericTask extends CompilerTask { | |
| @override | |
| final String name; | |
| GenericTask(this.name, Measurer measurer) : super(measurer); | |
| } | |
| class GenericTaskWithMetrics extends GenericTask { | |
| @override | |
| final Metrics metrics; | |
| GenericTaskWithMetrics(super.name, super.measurer, this.metrics); | |
| } | |
| class Measurer { | |
| /// Measures the total runtime from this object was constructed. | |
| /// | |
| /// Note: MUST be the first field of this class to ensure [wallclock] is | |
| /// started before other computations. | |
| final Stopwatch _wallClock = Stopwatch()..start(); | |
| Duration get elapsedWallClock => _wallClock.elapsed; | |
| /// Measures gaps between zoned closures due to asynchronicity. | |
| final Stopwatch _asyncWallClock = Stopwatch(); | |
| Duration get elapsedAsyncWallClock => _asyncWallClock.elapsed; | |
| /// Whether measurement of tasks is enabled. | |
| final bool enableTaskMeasurements; | |
| static int _hashCodeGenerator = 197; | |
| @override | |
| final int hashCode = _hashCodeGenerator++; | |
| @override | |
| bool operator ==(other) => identical(this, other); | |
| Measurer({this.enableTaskMeasurements = false}); | |
| /// The currently running task, that is, the task whose [Stopwatch] is | |
| /// currently running. | |
| CompilerTask? _currentTask; | |
| /// The current task which should be charged for asynchronous gaps. | |
| CompilerTask? _currentAsyncTask; | |
| /// Start counting the total elapsed time since the compiler started. | |
| void startWallClock() { | |
| _wallClock.start(); | |
| } | |
| /// Start counting the total elapsed time since the compiler started. | |
| void stopWallClock() { | |
| _wallClock.stop(); | |
| } | |
| /// Call this before returning to the eventloop. | |
| void startAsyncWallClock() { | |
| if (_currentAsyncTask != null) { | |
| _currentAsyncTask!._watch?.start(); | |
| } else { | |
| _asyncWallClock.start(); | |
| } | |
| } | |
| /// Call this when the eventloop returns control to us. | |
| void stopAsyncWallClock() { | |
| if (_currentAsyncTask != null) { | |
| _currentAsyncTask!._watch?.stop(); | |
| } | |
| _asyncWallClock.stop(); | |
| } | |
| } |