Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(108)

Side by Side Diff: pkg/compiler/lib/src/common/tasks.dart

Issue 1898043004: Use Zone to correctly measure async operations. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library dart2js.common.tasks; 5 library dart2js.common.tasks;
6 6
7 import 'dart:developer' show UserTag; 7 import 'dart:async' show
8 Future,
9 Zone,
10 ZoneDelegate,
11 ZoneSpecification,
12 runZoned;
8 13
9 import '../common.dart'; 14 import '../common.dart';
10 import '../compiler.dart' show Compiler; 15 import '../compiler.dart' show Compiler;
11 import '../elements/elements.dart' show Element; 16 import '../elements/elements.dart' show Element;
12 17
13 typedef void DeferredAction(); 18 typedef void DeferredAction();
14 19
15 class DeferredTask { 20 class DeferredTask {
16 final Element element; 21 final Element element;
17 final DeferredAction action; 22 final DeferredAction action;
18 23
19 DeferredTask(this.element, this.action); 24 DeferredTask(this.element, this.action);
20 } 25 }
21 26
27 /// A [CompilerTask] is used to measure where time is spent in the compiler.
28 /// The main entry points are [measure] and [measureIo].
22 class CompilerTask { 29 class CompilerTask {
23 final Compiler compiler; 30 final Compiler compiler;
24 final Stopwatch watch; 31 final Stopwatch watch;
25 UserTag profilerTag;
26 final Map<String, GenericTask> _subtasks = <String, GenericTask>{}; 32 final Map<String, GenericTask> _subtasks = <String, GenericTask>{};
27 33
34 int asyncCount = 0;
35
28 CompilerTask(Compiler compiler) 36 CompilerTask(Compiler compiler)
29 : this.compiler = compiler, 37 : this.compiler = compiler,
30 watch = (compiler.options.verbose) ? new Stopwatch() : null; 38 watch = (compiler.options.verbose) ? new Stopwatch() : null;
31 39
32 DiagnosticReporter get reporter => compiler.reporter; 40 DiagnosticReporter get reporter => compiler.reporter;
33 41
42 Measurer get measurer => compiler.measurer;
43
34 String get name => "Unknown task '${this.runtimeType}'"; 44 String get name => "Unknown task '${this.runtimeType}'";
35 45
46 bool get isRunning => watch?.isRunning == true;
47
36 int get timing { 48 int get timing {
37 if (watch == null) return 0; 49 if (watch == null) return 0;
38 int total = watch.elapsedMilliseconds; 50 int total = watch.elapsedMilliseconds;
39 for (GenericTask subtask in _subtasks.values) { 51 for (GenericTask subtask in _subtasks.values) {
40 total += subtask.timing; 52 total += subtask.timing;
41 } 53 }
42 return total; 54 return total;
43 } 55 }
44 56
45 UserTag getProfilerTag() { 57 Duration get duration {
46 if (profilerTag == null) profilerTag = new UserTag(name); 58 if (watch == null) return Duration.ZERO;
47 return profilerTag; 59 Duration total = watch.elapsed;
60 for (GenericTask subtask in _subtasks.values) {
61 total += subtask.duration;
62 }
63 return total;
48 } 64 }
49 65
50 measure(action()) { 66 /// Perform [action] and use [watch] to measure its runtime (including any
51 // In verbose mode when watch != null. 67 /// asynchronous callbacks, such as, [Future.then], but excluding code
52 if (watch == null) return action(); 68 /// measured by other tasks).
53 CompilerTask previous = compiler.measuredTask; 69 measure(action()) => watch == null ? action() : measureZoned(action);
54 if (identical(this, previous)) return action(); 70
55 compiler.measuredTask = this; 71 /// Helper method that starts measuring with this [CompilerTask], that is,
72 /// make this task the currently measured task.
73 CompilerTask start() {
74 if (watch == null) return null;
75 CompilerTask previous = measurer.currentTask;
76 measurer.currentTask = this;
56 if (previous != null) previous.watch.stop(); 77 if (previous != null) previous.watch.stop();
78 // Regardless of [previous] is `null` we've returned from the eventloop.
79 measurer.stopAsyncWallClock();
Johnni Winther 2016/04/25 09:23:02 'of [previous]' -> 'of whether [previous]'
ahe 2016/04/26 11:35:00 Done.
57 watch.start(); 80 watch.start();
58 UserTag oldTag = getProfilerTag().makeCurrent(); 81 return previous;
59 try {
60 return action();
61 } finally {
62 watch.stop();
63 oldTag.makeCurrent();
64 if (previous != null) previous.watch.start();
65 compiler.measuredTask = previous;
66 }
67 } 82 }
68 83
84 /// Helper method that stops measuring with this [CompilerTask], that is,
85 /// make [previous] the currently measured task.
86 void stop(CompilerTask previous) {
87 if (watch == null) return;
88 watch.stop();
89 if (previous != null) {
90 previous.watch.start();
91 } else {
92 // If there's no previous task, we're about to return control to the
93 // event loop. Start counting that as waiting asynchronous I/O.
94 measurer.startAsyncWallClock();
95 }
96 measurer.currentTask = previous;
97 }
98
99 /// Helper method for [measure]. Don't call this method directly as it
100 /// assumes that [watch] isn't null.
101 measureZoned(action()) {
102 // Using zones, we're able to track asynchronous operations correctly, as
103 // our zone will be asked to invoke `then` blocks. Then blocks (the closure
104 // passed to runZoned, and other closures) are run via the `run` functions
105 // below.
106
107 assert(watch != null);
108
109 // The current zone is already measuring `this` task.
110 if (Zone.current[measurer] == this) return action();
111
112 /// Run [f] in [zone]. Running must be delegated to [parent] to ensure that
113 /// various state is set up correctly (in particular that `Zone.current`
114 /// has the right value). Since [measureZoned] can be called recursively
115 /// (synchronously), some of the measuring zones we create will be parents
116 /// of other measuring zones, but we still need to call through the parent
117 /// chain. Consequently, we use a zone value keyed by [measurer] to see if
118 /// we should measure or not when delegating.
119 run(Zone self, ZoneDelegate parent, Zone zone, f()) {
120 if (zone[measurer] != this) return parent.run(zone, f);
121 CompilerTask previous = start();
122 try {
123 return parent.run(zone, f);
124 } finally {
125 stop(previous);
126 }
127 }
128
129 /// Same as [run] except that [f] takes one argument, [arg].
130 runUnary(Zone self, ZoneDelegate parent, Zone zone, f(arg), arg) {
131 if (zone[measurer] != this) return parent.runUnary(zone, f, arg);
132 CompilerTask previous = start();
133 try {
134 return parent.runUnary(zone, f, arg);
135 } finally {
136 stop(previous);
137 }
138 }
139
140 /// Same as [run] except that [f] takes two arguments ([a1] and [a2]).
141 runBinary(Zone self, ZoneDelegate parent, Zone zone, f(a1, a2), a1, a2) {
142 if (zone[measurer] != this) return parent.runBinary(zone, f, a1, a2);
143 CompilerTask previous = start();
144 try {
145 return parent.runBinary(zone, f, a1, a2);
146 } finally {
147 stop(previous);
148 }
149 }
150
151 return runZoned(
152 action,
153 zoneValues: { measurer: this },
154 zoneSpecification: new ZoneSpecification(
155 run: run, runUnary: runUnary, runBinary: runBinary));
156 }
157
158 /// Asynchronous version of [measure]. Use this when action returns a future
159 /// that's truly asynchronous, such I/O. Only one task can use this method
160 /// concurrently.
161 ///
162 /// Note: we assume that this method is used only by the compiler input
163 /// provider, but it could be used by other tasks as long as the input
164 /// provider will not be called by those tasks.
165 measureIo(Future action()) {
166 return watch == null ? action() : measureIoHelper(action);
167 }
168
169 /// Helper method for [measureIo]. Don't call this directly as it assumes
170 /// that [watch] isn't null.
171 Future measureIoHelper(Future action()) {
172 assert(watch != null);
173 if (measurer.currentAsyncTask == null) {
174 measurer.currentAsyncTask = this;
175 } else if (measurer.currentAsyncTask != this) {
176 throw "Can't track async task '$name' because"
177 " '${measurer.currentAsyncTask.name}' is already being tracked.";
178 }
179 asyncCount++;
180 return measure(action).whenComplete(() {
181 asyncCount--;
182 if (asyncCount == 0) measurer.currentAsyncTask = null;
183 });
184 }
185
186 /// Convenience function for combining
187 /// [DiagnosticReporter.withCurrentElement] and [measure].
69 measureElement(Element element, action()) { 188 measureElement(Element element, action()) {
70 reporter.withCurrentElement(element, () => measure(action)); 189 return watch == null
190 ? reporter.withCurrentElement(element, action)
191 : measureElementHelper(element, action);
192 }
193
194 /// Helper method for [measureElement]. Don't call this directly as it
195 /// assumes that [watch] isn't null.
196 measureElementHelper(Element element, action()) {
197 assert(watch != null);
198 return reporter.withCurrentElement(element, () => measure(action));
71 } 199 }
72 200
73 /// Measure the time spent in [action] (if in verbose mode) and accumulate it 201 /// Measure the time spent in [action] (if in verbose mode) and accumulate it
74 /// under a subtask with the given name. 202 /// under a subtask with the given name.
75 measureSubtask(String name, action()) { 203 measureSubtask(String name, action()) {
76 if (watch == null) return action(); 204 return watch == null ? action() : measureSubtaskHelper(name, action);
205 }
206
207 /// Helper method for [measureSubtask]. Don't call this directly as it
208 /// assumes that [watch] isn't null.
209 measureSubtaskHelper(String name, action()) {
210 assert(watch != null);
77 // Use a nested CompilerTask for the measurement to ensure nested [measure] 211 // Use a nested CompilerTask for the measurement to ensure nested [measure]
78 // calls work correctly. The subtasks will never themselves have nested 212 // calls work correctly. The subtasks will never themselves have nested
79 // subtasks because they are not accessible outside. 213 // subtasks because they are not accessible outside.
80 GenericTask subtask = 214 GenericTask subtask =
81 _subtasks.putIfAbsent(name, () => new GenericTask(name, compiler)); 215 _subtasks.putIfAbsent(name, () => new GenericTask(name, compiler));
82 return subtask.measure(action); 216 return subtask.measure(action);
83 } 217 }
84 218
85 Iterable<String> get subtasks => _subtasks.keys; 219 Iterable<String> get subtasks => _subtasks.keys;
86 220
87 int getSubtaskTime(String subtask) => _subtasks[subtask].timing; 221 int getSubtaskTime(String subtask) => _subtasks[subtask].timing;
222
223 bool getSubtaskIsRunning(String subtask) => _subtasks[subtask].isRunning;
88 } 224 }
89 225
90 class GenericTask extends CompilerTask { 226 class GenericTask extends CompilerTask {
91 final String name; 227 final String name;
92 228
93 GenericTask(this.name, Compiler compiler) : super(compiler); 229 GenericTask(this.name, Compiler compiler) : super(compiler);
94 } 230 }
231
232 class Measurer {
233 /// Measures the total runtime from this object was constructed.
234 ///
235 /// Note: MUST be first field to ensure [wallclock] is started before other
236 /// computations.
237 final Stopwatch wallClock = new Stopwatch()..start();
238
239 /// Measures gaps between zoned closures due to asynchronicity.
240 final Stopwatch asyncWallClock = new Stopwatch();
241
242 /// The currently running task, that is, the task whose [Stopwatch] is
243 /// currently running.
244 CompilerTask currentTask;
245
246 /// The current task which should be charged for asynchronous gaps.
247 CompilerTask currentAsyncTask;
248
249 /// Start counting the total elapsed time since the compiler started.
250 void startWallClock() {
251 wallClock.start();
252 }
253
254 /// Start counting the total elapsed time since the compiler started.
255 void stopWallClock() {
256 wallClock.stop();
257 }
258
259 /// Call this before returning to the eventloop.
260 void startAsyncWallClock() {
261 if (currentAsyncTask != null) {
262 currentAsyncTask.watch.start();
263 } else {
264 asyncWallClock.start();
265 }
266 }
267
268 /// Call this when the eventloop returns control to us.
269 void stopAsyncWallClock() {
270 if (currentAsyncTask != null) {
271 currentAsyncTask.watch.stop();
272 }
273 asyncWallClock.stop();
274 }
275 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698