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

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

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

Powered by Google App Engine
This is Rietveld 408576698