| // 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 'package:collection/collection.dart'; |
| |
| /// The performance of an operation. |
| abstract class OperationPerformance { |
| /// The child operations, might be empty. |
| List<OperationPerformance> get children; |
| |
| /// The data attachments, for non-timing data, e.g. how many files were read, |
| /// or how many bytes were processed. |
| List<OperationPerformanceData> get data; |
| |
| /// The duration of this operation, including its children. |
| Duration get elapsed; |
| |
| /// The duration of this operation, excluding its children. |
| Duration get elapsedSelf; |
| |
| /// The name of the operation. |
| String get name; |
| |
| OperationPerformance? getChild(String name); |
| |
| /// Write this operation and its children into the [buffer]. |
| void write({ |
| required StringBuffer buffer, |
| String indent = '', |
| }); |
| } |
| |
| /// The data attachment for a [OperationPerformance]. |
| abstract class OperationPerformanceData<T> { |
| String get name; |
| |
| T get value; |
| } |
| |
| abstract class OperationPerformanceDataImpl<T> |
| implements OperationPerformanceData<T> { |
| @override |
| final String name; |
| |
| OperationPerformanceDataImpl(this.name); |
| |
| @override |
| String toString() { |
| return '$name: $value'; |
| } |
| } |
| |
| class OperationPerformanceDataImpl_int |
| extends OperationPerformanceDataImpl<int> { |
| @override |
| int value = 0; |
| |
| OperationPerformanceDataImpl_int(String name) : super(name); |
| |
| void add(int item) { |
| value += item; |
| } |
| |
| void increment() { |
| value++; |
| } |
| } |
| |
| class OperationPerformanceImpl implements OperationPerformance { |
| @override |
| final String name; |
| |
| final Stopwatch _timer = Stopwatch(); |
| final List<OperationPerformanceImpl> _children = []; |
| |
| final Map<String, OperationPerformanceData<Object>> _data = {}; |
| |
| OperationPerformanceImpl(this.name); |
| |
| @override |
| List<OperationPerformance> get children { |
| return _children; |
| } |
| |
| @override |
| List<OperationPerformanceData<Object>> get data { |
| return _data.values.toList(); |
| } |
| |
| @override |
| Duration get elapsed { |
| return _timer.elapsed; |
| } |
| |
| @override |
| Duration get elapsedSelf { |
| return elapsed - _elapsedChildren; |
| } |
| |
| Duration get _elapsedChildren { |
| return children.fold<Duration>( |
| Duration.zero, |
| (sum, child) => sum + child.elapsed, |
| ); |
| } |
| |
| @override |
| OperationPerformanceImpl? getChild(String name) { |
| return _children.firstWhereOrNull( |
| (child) => child.name == name, |
| ); |
| } |
| |
| OperationPerformanceDataImpl_int getDataInt(String name) { |
| var data = _data[name]; |
| if (data is OperationPerformanceDataImpl_int) { |
| return data; |
| } else if (data != null) { |
| throw StateError('Not int: ${data.runtimeType}'); |
| } else { |
| return _data[name] = OperationPerformanceDataImpl_int(name); |
| } |
| } |
| |
| /// Run the [operation] as a child with the given [name]. |
| /// |
| /// If there is no such child, a new one is created, with a new timer. |
| /// |
| /// If there is already a child with that name, its timer will resume and |
| /// then stop. So, it will accumulate time across all runs. |
| T run<T>( |
| String name, |
| T Function(OperationPerformanceImpl) operation, |
| ) { |
| OperationPerformanceImpl child = _existingOrNewChild(name); |
| child._timer.start(); |
| |
| try { |
| return operation(child); |
| } finally { |
| child._timer.stop(); |
| } |
| } |
| |
| /// Run the [operation] as a child with the given [name]. |
| /// |
| /// If there is no such child, a new one is created, with a new timer. |
| /// |
| /// If there is already a child with that name, its timer will resume and |
| /// then stop. So, it will accumulate time across all runs. |
| Future<T> runAsync<T>( |
| String name, |
| Future<T> Function(OperationPerformanceImpl) operation, |
| ) async { |
| var child = _existingOrNewChild(name); |
| child._timer.start(); |
| |
| try { |
| return await operation(child); |
| } finally { |
| child._timer.stop(); |
| } |
| } |
| |
| @override |
| String toString() { |
| return '(name: $name, elapsed: $elapsed, elapsedSelf: $elapsedSelf)'; |
| } |
| |
| @override |
| void write({required StringBuffer buffer, String indent = ''}) { |
| buffer.write('$indent${toString()}'); |
| |
| if (_data.isNotEmpty) { |
| var sortedNames = _data.keys.toList()..sort(); |
| var sortedData = sortedNames.map((name) => _data[name]); |
| var dataStr = sortedData.map((e) => '$e').join(', '); |
| buffer.write('($dataStr)'); |
| } |
| |
| buffer.writeln(); |
| |
| var childIndent = '$indent '; |
| for (var child in children) { |
| child.write(buffer: buffer, indent: childIndent); |
| } |
| } |
| |
| OperationPerformanceImpl _existingOrNewChild(String name) { |
| var child = getChild(name); |
| if (child == null) { |
| child = OperationPerformanceImpl(name); |
| _children.add(child); |
| } |
| return child; |
| } |
| } |