Create timing package (dart-lang/timing#1)

diff --git a/pkgs/timing/.gitignore b/pkgs/timing/.gitignore
new file mode 100644
index 0000000..1ddf798
--- /dev/null
+++ b/pkgs/timing/.gitignore
@@ -0,0 +1,7 @@
+.packages
+/build/
+pubspec.lock
+
+# Files generated by dart tools
+.dart_tool
+doc/
diff --git a/pkgs/timing/.travis.yml b/pkgs/timing/.travis.yml
new file mode 100644
index 0000000..77141bf
--- /dev/null
+++ b/pkgs/timing/.travis.yml
@@ -0,0 +1,17 @@
+language: dart
+
+dart:
+  - dev
+
+dart_task:
+- dartfmt
+- dartanalyzer: --fatal-infos --fatal-warnings .
+- test
+
+branches:
+  only:
+  - master
+
+cache:
+  directories:
+  - $HOME/.pub-cache
diff --git a/pkgs/timing/CHANGELOG.md b/pkgs/timing/CHANGELOG.md
new file mode 100644
index 0000000..090fc36
--- /dev/null
+++ b/pkgs/timing/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.1.0
+
+- Initial release
diff --git a/pkgs/timing/LICENSE b/pkgs/timing/LICENSE
new file mode 100644
index 0000000..c4dc9ba
--- /dev/null
+++ b/pkgs/timing/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2018, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/timing/README.md b/pkgs/timing/README.md
new file mode 100644
index 0000000..a1518ea
--- /dev/null
+++ b/pkgs/timing/README.md
@@ -0,0 +1,13 @@
+# [![Build Status](https://travis-ci.org/dart-lang/timing.svg?branch=master)](https://travis-ci.org/dart-lang/timing)
+
+Timing is a simple package for tracking performance of both async and sync actions
+
+```dart
+var tracker = AsyncTimeTracker();
+await tracker.track(() async {
+  // some async code here
+});
+
+// Use results
+print('${tracker.duration} ${tracker.innerDuration} ${tracker.slices}');
+```
\ No newline at end of file
diff --git a/pkgs/timing/lib/src/clock.dart b/pkgs/timing/lib/src/clock.dart
new file mode 100644
index 0000000..1974c4a
--- /dev/null
+++ b/pkgs/timing/lib/src/clock.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2017, 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';
+
+/// A function that returns the current [DateTime].
+typedef DateTime _Clock();
+DateTime _defaultClock() => DateTime.now();
+
+const _ZoneKey = #timing_Clock;
+
+/// Returns the current [DateTime].
+///
+/// May be overridden for tests using [scopeClock].
+DateTime now() => (Zone.current[_ZoneKey] as _Clock ?? _defaultClock)();
+
+/// Runs [f], with [clock] scoped whenever [now] is called.
+T scopeClock<T>(DateTime clock(), T f()) =>
+    runZoned(f, zoneValues: {_ZoneKey: clock});
diff --git a/pkgs/timing/lib/src/timing.dart b/pkgs/timing/lib/src/timing.dart
new file mode 100644
index 0000000..a1d5a85
--- /dev/null
+++ b/pkgs/timing/lib/src/timing.dart
@@ -0,0 +1,309 @@
+// Copyright (c) 2018, 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 'clock.dart';
+
+/// The timings of an operation, including its [startTime], [stopTime], and
+/// [duration].
+class TimeSlice {
+  /// The total duration of this operation, equivalent to taking the difference
+  /// between [stopTime] and [startTime].
+  Duration get duration => stopTime.difference(startTime);
+
+  final DateTime startTime;
+
+  final DateTime stopTime;
+
+  TimeSlice(this.startTime, this.stopTime);
+
+  @override
+  String toString() => '($startTime + $duration)';
+}
+
+/// The timings of an async operation, consist of several sync [slices] and
+/// includes total [startTime], [stopTime], and [duration].
+class TimeSliceGroup implements TimeSlice {
+  final List<TimeSlice> slices;
+
+  @override
+  DateTime get startTime => slices.first.startTime;
+
+  @override
+  DateTime get stopTime => slices.last.stopTime;
+
+  /// The total duration of this operation, equivalent to taking the difference
+  /// between [stopTime] and [startTime].
+  @override
+  Duration get duration => stopTime.difference(startTime);
+
+  /// Sum of [duration]s of all [slices].
+  ///
+  /// If some of slices implements [TimeSliceGroup] [innerDuration] will be used
+  /// to compute sum.
+  Duration get innerDuration => slices.fold(
+      Duration.zero,
+      (duration, slice) =>
+          duration +
+          (slice is TimeSliceGroup ? slice.innerDuration : slice.duration));
+
+  TimeSliceGroup(List<TimeSlice> this.slices);
+
+  @override
+  String toString() => slices.toString();
+}
+
+abstract class TimeTracker implements TimeSlice {
+  /// Whether tracking is active.
+  ///
+  /// Tracking is only active after `isStarted` and before `isFinished`.
+  bool get isTracking;
+
+  /// Whether tracking is finished.
+  ///
+  /// Tracker can't be used as [TimeSlice] before it is finished
+  bool get isFinished;
+
+  /// Whether tracking was started.
+  ///
+  /// Equivalent of `isTracking || isFinished`
+  bool get isStarted;
+
+  T track<T>(T Function() action);
+}
+
+/// Tracks only sync actions
+class SyncTimeTracker implements TimeTracker {
+  /// When this operation started, call [_start] to set this.
+  @override
+  DateTime get startTime => _startTime;
+  DateTime _startTime;
+
+  /// When this operation stopped, call [_stop] to set this.
+  @override
+  DateTime get stopTime => _stopTime;
+  DateTime _stopTime;
+
+  /// Start tracking this operation, must only be called once, before [_stop].
+  void _start() {
+    assert(_startTime == null && _stopTime == null);
+    _startTime = now();
+  }
+
+  /// Stop tracking this operation, must only be called once, after [_start].
+  void _stop() {
+    assert(_startTime != null && _stopTime == null);
+    _stopTime = now();
+  }
+
+  /// Splits tracker into two slices
+  ///
+  /// Returns new [TimeSlice] started on [startTime] and ended now.
+  /// Modifies [startTime] of tracker to current time point
+  ///
+  /// Don't change state of tracker. Can be called only while [isTracking], and
+  /// tracker will sill be tracking after call.
+  TimeSlice _split() {
+    if (!isTracking) {
+      throw StateError('Can be only called while tracking');
+    }
+    var _now = now();
+    var prevSlice = TimeSlice(_startTime, _now);
+    _startTime = _now;
+    return prevSlice;
+  }
+
+  @override
+  T track<T>(T Function() action) {
+    if (isStarted) {
+      throw StateError('Can not be tracked twice');
+    }
+    _start();
+    try {
+      return action();
+    } finally {
+      _stop();
+    }
+  }
+
+  @override
+  bool get isStarted => startTime != null;
+
+  @override
+  bool get isTracking => startTime != null && stopTime == null;
+
+  @override
+  bool get isFinished => startTime != null && stopTime != null;
+
+  @override
+  Duration get duration => stopTime?.difference(startTime);
+}
+
+/// Async actions returning [Future] will be tracked as single sync time span
+/// from the beginning of execution till completion of future
+class SimpleAsyncTimeTracker extends SyncTimeTracker {
+  @override
+  T track<T>(T Function() action) {
+    if (isStarted) {
+      throw StateError('Can not be tracked twice');
+    }
+    T result;
+    _start();
+    try {
+      result = action();
+    } catch (_) {
+      _stop();
+      rethrow;
+    }
+    if (result is Future) {
+      return result.whenComplete(_stop) as T;
+    } else {
+      _stop();
+      return result;
+    }
+  }
+}
+
+/// No-op implementation of [SyncTimeTracker] that does nothing.
+class NoOpTimeTracker implements TimeTracker {
+  static final sharedInstance = NoOpTimeTracker();
+
+  @override
+  Duration get duration =>
+      throw UnsupportedError('Unsupported in no-op implementation');
+
+  @override
+  DateTime get startTime =>
+      throw UnsupportedError('Unsupported in no-op implementation');
+
+  @override
+  DateTime get stopTime =>
+      throw UnsupportedError('Unsupported in no-op implementation');
+
+  @override
+  bool get isStarted =>
+      throw UnsupportedError('Unsupported in no-op implementation');
+
+  @override
+  bool get isTracking =>
+      throw UnsupportedError('Unsupported in no-op implementation');
+
+  @override
+  bool get isFinished =>
+      throw UnsupportedError('Unsupported in no-op implementation');
+
+  @override
+  T track<T>(T Function() action) => action();
+}
+
+/// Track all async execution as disjoint time [slices] in ascending order.
+///
+/// Can [track] both async and sync actions.
+/// Can exclude time of tested trackers.
+///
+/// If tracked action spawns some dangled async executions behavior is't
+/// defined. Tracked might or might not track time of such executions
+class AsyncTimeTracker extends TimeSliceGroup implements TimeTracker {
+  final bool trackNested;
+
+  static const _ZoneKey = #timing_AsyncTimeTracker;
+
+  AsyncTimeTracker({this.trackNested = true}) : super([]);
+
+  T _trackSyncSlice<T>(ZoneDelegate parent, Zone zone, T Function() action) {
+    // Ignore dangling runs after tracker completes
+    if (isFinished) {
+      return action();
+    }
+
+    var isNestedRun = slices.isNotEmpty &&
+        slices.last is SyncTimeTracker &&
+        (slices.last as SyncTimeTracker).isTracking;
+    var isExcludedNestedTrack = !trackNested && zone[_ZoneKey] != this;
+
+    // Exclude nested sync tracks
+    if (isNestedRun && isExcludedNestedTrack) {
+      var timer = slices.last as SyncTimeTracker;
+      // Split already tracked time into new slice.
+      // Replace tracker in slices.last with splitted slice, to indicate for
+      // recursive calls that we not tracking.
+      slices.last = parent.run(zone, timer._split);
+      try {
+        return action();
+      } finally {
+        // Split tracker again and discard slice that was spend in nested tracker
+        parent.run(zone, timer._split);
+        // Add tracker back to list of slices and continue tracking
+        slices.add(timer);
+      }
+    }
+
+    // Exclude nested async tracks
+    if (isExcludedNestedTrack) {
+      return action();
+    }
+
+    // Split time slices in nested sync runs
+    if (isNestedRun) {
+      return action();
+    }
+
+    var timer = SyncTimeTracker();
+    slices.add(timer);
+
+    // Pass to parent zone, in case of overwritten clock
+    return parent.runUnary(zone, timer.track, action);
+  }
+
+  static final asyncTimeTrackerZoneSpecification = ZoneSpecification(
+    run: <R>(Zone self, ZoneDelegate parent, Zone zone, R Function() f) {
+      var tracker = self[_ZoneKey] as AsyncTimeTracker;
+      return tracker._trackSyncSlice(parent, zone, () => parent.run(zone, f));
+    },
+    runUnary: <R, T>(Zone self, ZoneDelegate parent, Zone zone, R Function(T) f,
+        T arg) {
+      var tracker = self[_ZoneKey] as AsyncTimeTracker;
+      return tracker._trackSyncSlice(
+          parent, zone, () => parent.runUnary(zone, f, arg));
+    },
+    runBinary: <R, T1, T2>(Zone self, ZoneDelegate parent, Zone zone,
+        R Function(T1, T2) f, T1 arg1, T2 arg2) {
+      var tracker = self[_ZoneKey] as AsyncTimeTracker;
+      return tracker._trackSyncSlice(
+          parent, zone, () => parent.runBinary(zone, f, arg1, arg2));
+    },
+  );
+
+  @override
+  T track<T>(T Function() action) {
+    if (isStarted) {
+      throw StateError('Can not be tracked twice');
+    }
+    _tracking = true;
+    var result = runZoned(action,
+        zoneSpecification: asyncTimeTrackerZoneSpecification,
+        zoneValues: {_ZoneKey: this});
+    if (result is Future) {
+      return result
+          // Break possible sync processing of future completion, so slice trackers can be finished
+          .whenComplete(() => Future.value())
+          .whenComplete(() => _tracking = false) as T;
+    } else {
+      _tracking = false;
+      return result;
+    }
+  }
+
+  bool _tracking;
+
+  @override
+  bool get isStarted => _tracking != null;
+
+  @override
+  bool get isFinished => _tracking == false;
+
+  @override
+  bool get isTracking => _tracking == true;
+}
diff --git a/pkgs/timing/lib/timing.dart b/pkgs/timing/lib/timing.dart
new file mode 100644
index 0000000..0163e8d
--- /dev/null
+++ b/pkgs/timing/lib/timing.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2018, 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.
+
+export 'src/timing.dart'
+    show
+        TimeSlice,
+        TimeSliceGroup,
+        TimeTracker,
+        SyncTimeTracker,
+        SimpleAsyncTimeTracker,
+        AsyncTimeTracker,
+        NoOpTimeTracker;
diff --git a/pkgs/timing/pubspec.yaml b/pkgs/timing/pubspec.yaml
new file mode 100644
index 0000000..9840526
--- /dev/null
+++ b/pkgs/timing/pubspec.yaml
@@ -0,0 +1,11 @@
+name: timing
+version: 0.1.0-dev
+description:
+author: Dart Team <misc@dartlang.org>
+homepage: https://github.com/dart-lang/timing
+
+environment:
+  sdk: ">=2.0.0 <3.0.0"
+
+dev_dependencies:
+  test: ^1.0.0
diff --git a/pkgs/timing/test/timing_test.dart b/pkgs/timing/test/timing_test.dart
new file mode 100644
index 0000000..9f3f3ac
--- /dev/null
+++ b/pkgs/timing/test/timing_test.dart
@@ -0,0 +1,413 @@
+// Copyright (c) 2018, 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 'package:test/test.dart';
+import 'package:timing/src/clock.dart';
+import 'package:timing/src/timing.dart';
+
+_noop() {}
+
+main() {
+  DateTime time;
+  final startTime = DateTime(2017);
+  DateTime fakeClock() => time;
+
+  TimeTracker tracker;
+  TimeTracker nestedTracker;
+
+  T scopedTrack<T>(T f()) => scopeClock(fakeClock, () => tracker.track(f));
+
+  setUp(() {
+    time = startTime;
+  });
+
+  canHandleSync([additionalExpects() = _noop]) {
+    test('Can track sync code', () {
+      expect(tracker.isStarted, false);
+      expect(tracker.isTracking, false);
+      expect(tracker.isFinished, false);
+      scopedTrack(() {
+        expect(tracker.isStarted, true);
+        expect(tracker.isTracking, true);
+        expect(tracker.isFinished, false);
+        time = time.add(const Duration(seconds: 5));
+      });
+      expect(tracker.isStarted, true);
+      expect(tracker.isTracking, false);
+      expect(tracker.isFinished, true);
+      expect(tracker.startTime, startTime);
+      expect(tracker.stopTime, time);
+      expect(tracker.duration, const Duration(seconds: 5));
+      additionalExpects();
+    });
+
+    test('Can track handled sync exceptions', () async {
+      scopedTrack(() {
+        try {
+          time = time.add(const Duration(seconds: 4));
+          throw 'error';
+        } on String {
+          time = time.add(const Duration(seconds: 1));
+        }
+      });
+      expect(tracker.isFinished, true);
+      expect(tracker.startTime, startTime);
+      expect(tracker.stopTime, time);
+      expect(tracker.duration, const Duration(seconds: 5));
+      additionalExpects();
+    });
+
+    test('Can track in case of unhandled sync exceptions', () async {
+      expect(
+          () => scopedTrack(() {
+                time = time.add(const Duration(seconds: 5));
+                throw 'error';
+              }),
+          throwsA(TypeMatcher<String>()));
+      expect(tracker.startTime, startTime);
+      expect(tracker.stopTime, time);
+      expect(tracker.duration, const Duration(seconds: 5));
+      additionalExpects();
+    });
+
+    test('Can be nested sync', () {
+      scopedTrack(() {
+        time = time.add(const Duration(seconds: 1));
+        nestedTracker.track(() {
+          time = time.add(const Duration(seconds: 2));
+        });
+        time = time.add(const Duration(seconds: 4));
+      });
+      expect(tracker.isFinished, true);
+      expect(tracker.startTime, startTime);
+      expect(tracker.stopTime, time);
+      expect(tracker.duration, const Duration(seconds: 7));
+      expect(nestedTracker.startTime.isAfter(startTime), true);
+      expect(nestedTracker.stopTime.isBefore(time), true);
+      expect(nestedTracker.duration, const Duration(seconds: 2));
+      additionalExpects();
+    });
+  }
+
+  canHandleAsync([additionalExpects() = _noop]) {
+    test('Can track async code', () async {
+      expect(tracker.isStarted, false);
+      expect(tracker.isTracking, false);
+      expect(tracker.isFinished, false);
+      await scopedTrack(() => Future(() {
+            expect(tracker.isStarted, true);
+            expect(tracker.isTracking, true);
+            expect(tracker.isFinished, false);
+            time = time.add(const Duration(seconds: 5));
+          }));
+      expect(tracker.isStarted, true);
+      expect(tracker.isTracking, false);
+      expect(tracker.isFinished, true);
+      expect(tracker.startTime, startTime);
+      expect(tracker.stopTime, time);
+      expect(tracker.duration, const Duration(seconds: 5));
+      additionalExpects();
+    });
+
+    test('Can track handled async exceptions', () async {
+      await scopedTrack(() {
+        time = time.add(const Duration(seconds: 1));
+        return Future(() {
+          time = time.add(const Duration(seconds: 2));
+          throw 'error';
+        }).then((_) {
+          time = time.add(const Duration(seconds: 4));
+        }).catchError((error, stack) {
+          time = time.add(const Duration(seconds: 8));
+        });
+      });
+      expect(tracker.isFinished, true);
+      expect(tracker.startTime, startTime);
+      expect(tracker.stopTime, time);
+      expect(tracker.duration, const Duration(seconds: 11));
+      additionalExpects();
+    });
+
+    test('Can track in case of unhandled async exceptions', () async {
+      var future = scopedTrack(() {
+        time = time.add(const Duration(seconds: 1));
+        return Future(() {
+          time = time.add(const Duration(seconds: 2));
+          throw 'error';
+        }).then((_) {
+          time = time.add(const Duration(seconds: 4));
+        });
+      });
+      await expectLater(future, throwsA(TypeMatcher<String>()));
+      expect(tracker.isFinished, true);
+      expect(tracker.startTime, startTime);
+      expect(tracker.stopTime, time);
+      expect(tracker.duration, const Duration(seconds: 3));
+      additionalExpects();
+    });
+
+    test('Can be nested async', () async {
+      await scopedTrack(() async {
+        time = time.add(const Duration(milliseconds: 1));
+        await Future.value();
+        time = time.add(const Duration(milliseconds: 2));
+        await nestedTracker.track(() async {
+          time = time.add(const Duration(milliseconds: 4));
+          await Future.value();
+          time = time.add(const Duration(milliseconds: 8));
+          await Future.value();
+          time = time.add(const Duration(milliseconds: 16));
+        });
+        time = time.add(const Duration(milliseconds: 32));
+        await Future.value();
+        time = time.add(const Duration(milliseconds: 64));
+      });
+      expect(tracker.isFinished, true);
+      expect(tracker.startTime, startTime);
+      expect(tracker.stopTime, time);
+      expect(tracker.duration, const Duration(milliseconds: 127));
+      expect(nestedTracker.startTime.isAfter(startTime), true);
+      expect(nestedTracker.stopTime.isBefore(time), true);
+      expect(nestedTracker.duration, const Duration(milliseconds: 28));
+      additionalExpects();
+    });
+  }
+
+  group('SyncTimeTracker', () {
+    setUp(() {
+      tracker = SyncTimeTracker();
+      nestedTracker = SyncTimeTracker();
+    });
+
+    canHandleSync();
+
+    test('Can not track async code', () async {
+      await scopedTrack(() => Future(() {
+            time = time.add(const Duration(seconds: 5));
+          }));
+      expect(tracker.isFinished, true);
+      expect(tracker.startTime, startTime);
+      expect(tracker.stopTime, startTime);
+      expect(tracker.duration, const Duration(seconds: 0));
+    });
+  });
+
+  group('AsyncTimeTracker.simple', () {
+    setUp(() {
+      tracker = SimpleAsyncTimeTracker();
+      nestedTracker = SimpleAsyncTimeTracker();
+    });
+
+    canHandleSync();
+
+    canHandleAsync();
+
+    test('Can not distinguish own async code', () async {
+      var future = scopedTrack(() => Future(() {
+            time = time.add(const Duration(seconds: 5));
+          }));
+      time = time.add(const Duration(seconds: 10));
+      await future;
+      expect(tracker.isFinished, true);
+      expect(tracker.startTime, startTime);
+      expect(tracker.stopTime, time);
+      expect(tracker.duration, const Duration(seconds: 15));
+    });
+  });
+
+  group('AsyncTimeTracker', () {
+    AsyncTimeTracker asyncTracker;
+    AsyncTimeTracker nestedAsyncTracker;
+    setUp(() {
+      tracker = asyncTracker = AsyncTimeTracker();
+      nestedTracker = nestedAsyncTracker = AsyncTimeTracker();
+    });
+
+    canHandleSync(() {
+      expect(asyncTracker.innerDuration, asyncTracker.duration);
+      expect(asyncTracker.slices.length, 1);
+    });
+
+    canHandleAsync(() {
+      expect(asyncTracker.innerDuration, asyncTracker.duration);
+      expect(asyncTracker.slices.length, greaterThan(1));
+    });
+
+    test('Can track complex async innerDuration', () async {
+      var completer = Completer();
+      var future = scopedTrack(() async {
+        time = time.add(const Duration(seconds: 1)); // Tracked sync
+        await Future.value();
+        time = time.add(const Duration(seconds: 2)); // Tracked async
+        await completer.future;
+        time = time.add(const Duration(seconds: 4)); // Tracked async, delayed
+      }).then((_) {
+        time = time.add(const Duration(seconds: 8)); // Async, after tracking
+      });
+      time = time.add(const Duration(seconds: 16)); // Sync, between slices
+
+      await Future(() {
+        // Async, between slices
+        time = time.add(const Duration(seconds: 32));
+        completer.complete();
+      });
+      await future;
+      expect(asyncTracker.isFinished, true);
+      expect(asyncTracker.startTime, startTime);
+      expect(asyncTracker.stopTime.isBefore(time), true);
+      expect(asyncTracker.duration, const Duration(seconds: 55));
+      expect(asyncTracker.innerDuration, const Duration(seconds: 7));
+      expect(asyncTracker.slices.length, greaterThan(1));
+    });
+
+    test('Can exclude nested sync', () {
+      tracker = asyncTracker = AsyncTimeTracker(trackNested: false);
+      scopedTrack(() {
+        time = time.add(const Duration(seconds: 1));
+        nestedAsyncTracker.track(() {
+          time = time.add(const Duration(seconds: 2));
+        });
+        time = time.add(const Duration(seconds: 4));
+      });
+      expect(asyncTracker.isFinished, true);
+      expect(asyncTracker.startTime, startTime);
+      expect(asyncTracker.stopTime, time);
+      expect(asyncTracker.duration, const Duration(seconds: 7));
+      expect(asyncTracker.innerDuration, const Duration(seconds: 5));
+      expect(asyncTracker.slices.length, greaterThan(1));
+      expect(nestedAsyncTracker.startTime.isAfter(startTime), true);
+      expect(nestedAsyncTracker.stopTime.isBefore(time), true);
+      expect(nestedAsyncTracker.duration, const Duration(seconds: 2));
+      expect(nestedAsyncTracker.innerDuration, const Duration(seconds: 2));
+      expect(nestedAsyncTracker.slices.length, 1);
+    });
+
+    test('Can exclude complex nested sync', () {
+      tracker = asyncTracker = AsyncTimeTracker(trackNested: false);
+      nestedAsyncTracker = AsyncTimeTracker(trackNested: false);
+      var nestedAsyncTracker2 = AsyncTimeTracker(trackNested: false);
+      scopedTrack(() {
+        time = time.add(const Duration(seconds: 1));
+        nestedAsyncTracker.track(() {
+          time = time.add(const Duration(seconds: 2));
+          nestedAsyncTracker2.track(() {
+            time = time.add(const Duration(seconds: 4));
+          });
+          time = time.add(const Duration(seconds: 8));
+        });
+        time = time.add(const Duration(seconds: 16));
+      });
+      expect(asyncTracker.isFinished, true);
+      expect(asyncTracker.startTime, startTime);
+      expect(asyncTracker.stopTime, time);
+      expect(asyncTracker.duration, const Duration(seconds: 31));
+      expect(asyncTracker.innerDuration, const Duration(seconds: 17));
+      expect(asyncTracker.slices.length, greaterThan(1));
+      expect(nestedAsyncTracker.startTime.isAfter(startTime), true);
+      expect(nestedAsyncTracker.stopTime.isBefore(time), true);
+      expect(nestedAsyncTracker.duration, const Duration(seconds: 14));
+      expect(nestedAsyncTracker.innerDuration, const Duration(seconds: 10));
+      expect(nestedAsyncTracker.slices.length, greaterThan(1));
+      expect(nestedAsyncTracker2.startTime.isAfter(startTime), true);
+      expect(nestedAsyncTracker2.stopTime.isBefore(time), true);
+      expect(nestedAsyncTracker2.duration, const Duration(seconds: 4));
+      expect(nestedAsyncTracker2.innerDuration, const Duration(seconds: 4));
+      expect(nestedAsyncTracker2.slices.length, 1);
+    });
+
+    test(
+        'Can track all on grand-parent level and '
+        'exclude grand-childrens from parent', () {
+      tracker = asyncTracker = AsyncTimeTracker(trackNested: true);
+      nestedAsyncTracker = AsyncTimeTracker(trackNested: false);
+      var nestedAsyncTracker2 = AsyncTimeTracker();
+      scopedTrack(() {
+        time = time.add(const Duration(seconds: 1));
+        nestedAsyncTracker.track(() {
+          time = time.add(const Duration(seconds: 2));
+          nestedAsyncTracker2.track(() {
+            time = time.add(const Duration(seconds: 4));
+          });
+          time = time.add(const Duration(seconds: 8));
+        });
+        time = time.add(const Duration(seconds: 16));
+      });
+      expect(asyncTracker.isFinished, true);
+      expect(asyncTracker.startTime, startTime);
+      expect(asyncTracker.stopTime, time);
+      expect(asyncTracker.duration, const Duration(seconds: 31));
+      expect(asyncTracker.innerDuration, const Duration(seconds: 31));
+      expect(asyncTracker.slices.length, 1);
+      expect(nestedAsyncTracker.startTime.isAfter(startTime), true);
+      expect(nestedAsyncTracker.stopTime.isBefore(time), true);
+      expect(nestedAsyncTracker.duration, const Duration(seconds: 14));
+      expect(nestedAsyncTracker.innerDuration, const Duration(seconds: 10));
+      expect(nestedAsyncTracker.slices.length, greaterThan(1));
+      expect(nestedAsyncTracker2.startTime.isAfter(startTime), true);
+      expect(nestedAsyncTracker2.stopTime.isBefore(time), true);
+      expect(nestedAsyncTracker2.duration, const Duration(seconds: 4));
+      expect(nestedAsyncTracker2.innerDuration, const Duration(seconds: 4));
+      expect(nestedAsyncTracker2.slices.length, 1);
+    });
+
+    test('Can exclude nested async', () async {
+      tracker = asyncTracker = AsyncTimeTracker(trackNested: false);
+      await scopedTrack(() async {
+        time = time.add(const Duration(seconds: 1));
+        await nestedAsyncTracker.track(() async {
+          time = time.add(const Duration(seconds: 2));
+          await Future.value();
+          time = time.add(const Duration(seconds: 4));
+          await Future.value();
+          time = time.add(const Duration(seconds: 8));
+        });
+        time = time.add(const Duration(seconds: 16));
+      });
+      expect(asyncTracker.isFinished, true);
+      expect(asyncTracker.startTime, startTime);
+      expect(asyncTracker.stopTime, time);
+      expect(asyncTracker.duration, const Duration(seconds: 31));
+      expect(asyncTracker.innerDuration, const Duration(seconds: 17));
+      expect(asyncTracker.slices.length, greaterThan(1));
+      expect(nestedAsyncTracker.startTime.isAfter(startTime), true);
+      expect(nestedAsyncTracker.stopTime.isBefore(time), true);
+      expect(nestedAsyncTracker.duration, const Duration(seconds: 14));
+      expect(nestedAsyncTracker.innerDuration, const Duration(seconds: 14));
+      expect(nestedAsyncTracker.slices.length, greaterThan(1));
+    });
+
+    test('Can handle callbacks in excluded nested async', () async {
+      tracker = asyncTracker = AsyncTimeTracker(trackNested: false);
+      await scopedTrack(() async {
+        time = time.add(const Duration(seconds: 1));
+        var completer = Completer();
+        var future = completer.future.then((_) {
+          time = time.add(const Duration(seconds: 2));
+        });
+        await nestedAsyncTracker.track(() async {
+          time = time.add(const Duration(seconds: 4));
+          await Future.value();
+          time = time.add(const Duration(seconds: 8));
+          completer.complete();
+          await future;
+          time = time.add(const Duration(seconds: 16));
+        });
+        time = time.add(const Duration(seconds: 32));
+      });
+      expect(asyncTracker.isFinished, true);
+      expect(asyncTracker.startTime, startTime);
+      expect(asyncTracker.stopTime, time);
+      expect(asyncTracker.duration, const Duration(seconds: 63));
+      expect(asyncTracker.innerDuration, const Duration(seconds: 35));
+      expect(asyncTracker.slices.length, greaterThan(1));
+      expect(nestedAsyncTracker.startTime.isAfter(startTime), true);
+      expect(nestedAsyncTracker.stopTime.isBefore(time), true);
+      expect(nestedAsyncTracker.duration, const Duration(seconds: 30));
+      expect(nestedAsyncTracker.innerDuration, const Duration(seconds: 28));
+      expect(nestedAsyncTracker.slices.length, greaterThan(1));
+    });
+  });
+}