OLD | NEW |
---|---|
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' | 7 import 'dart:async' |
8 show Future, Zone, ZoneDelegate, ZoneSpecification, runZoned; | 8 show Future, Zone, ZoneDelegate, ZoneSpecification, runZoned; |
9 | 9 |
10 import '../common.dart'; | 10 /// Used to measure where time is spent in the compiler. |
11 import '../compiler.dart' show Compiler; | 11 /// |
12 import '../elements/elements.dart' show Element; | 12 /// This exposes [measure] and [measureIo], which wrap an action and assotiate |
Harry Terkelsen
2016/05/25 15:51:36
assotiate -> associate
Siggi Cherem (dart-lang)
2016/05/25 16:11:18
Done.
| |
13 | 13 /// the time spend during that action with this task. Nested measurements can be |
Harry Terkelsen
2016/05/25 15:51:36
spend -> spent
Siggi Cherem (dart-lang)
2016/05/25 16:11:17
Done.
| |
14 typedef void DeferredAction(); | 14 /// introduced by using [measureSubtask]. |
15 | 15 // TODO(sigmund): rename to MeasurableTask |
16 class DeferredTask { | 16 abstract class CompilerTask { |
Siggi Cherem (dart-lang)
2016/05/25 01:45:17
this is unrelated to the other kind of task, I mov
Johnni Winther
2016/05/25 09:02:00
Acknowledged.
| |
17 final Element element; | 17 final Measurer measurer; |
18 final DeferredAction action; | 18 final Stopwatch _watch; |
19 | |
20 DeferredTask(this.element, this.action); | |
21 } | |
22 | |
23 /// A [CompilerTask] is used to measure where time is spent in the compiler. | |
24 /// The main entry points are [measure] and [measureIo]. | |
25 class CompilerTask { | |
26 final Compiler compiler; | |
27 final Stopwatch watch; | |
28 final Map<String, GenericTask> _subtasks = <String, GenericTask>{}; | 19 final Map<String, GenericTask> _subtasks = <String, GenericTask>{}; |
29 | 20 |
30 int asyncCount = 0; | 21 int asyncCount = 0; |
31 | 22 |
32 CompilerTask(Compiler compiler) | 23 CompilerTask(Measurer measurer) |
33 : this.compiler = compiler, | 24 : measurer = measurer, |
34 watch = (compiler.options.verbose) ? new Stopwatch() : null; | 25 _watch = measurer.enableTaskMeasurements ? new Stopwatch() : null; |
35 | 26 |
36 DiagnosticReporter get reporter => compiler.reporter; | 27 /// Whether measurement is disabled. The functions [measure] and [measureIo] |
28 /// only measure time if measurements are enabled. | |
29 bool get _isDisabled => _watch == null; | |
37 | 30 |
38 Measurer get measurer => compiler.measurer; | 31 /// Name to use for reporting timing information. |
39 | 32 // Note: it would be sufficient to mark this getter abstract if we had enough |
Harry Terkelsen
2016/05/25 15:51:36
this note seems more like a TODO
Siggi Cherem (dart-lang)
2016/05/25 16:11:17
Yeah, not sure if it's worth deleting. Rephrase th
| |
33 // static checking to ensure that all subclasses override it. | |
40 String get name => "Unknown task '${this.runtimeType}'"; | 34 String get name => "Unknown task '${this.runtimeType}'"; |
41 | 35 |
42 bool get isRunning => watch?.isRunning == true; | 36 bool get isRunning => _watch?.isRunning == true; |
43 | 37 |
44 int get timing { | 38 int get timing { |
45 if (watch == null) return 0; | 39 if (_isDisabled) return 0; |
46 int total = watch.elapsedMilliseconds; | 40 int total = _watch.elapsedMilliseconds; |
47 for (GenericTask subtask in _subtasks.values) { | 41 for (GenericTask subtask in _subtasks.values) { |
48 total += subtask.timing; | 42 total += subtask.timing; |
49 } | 43 } |
50 return total; | 44 return total; |
51 } | 45 } |
52 | 46 |
53 Duration get duration { | 47 Duration get duration { |
54 if (watch == null) return Duration.ZERO; | 48 if (_isDisabled) return Duration.ZERO; |
55 Duration total = watch.elapsed; | 49 Duration total = _watch.elapsed; |
56 for (GenericTask subtask in _subtasks.values) { | 50 for (GenericTask subtask in _subtasks.values) { |
57 total += subtask.duration; | 51 total += subtask.duration; |
58 } | 52 } |
59 return total; | 53 return total; |
60 } | 54 } |
61 | 55 |
62 /// Perform [action] and use [watch] to measure its runtime (including any | 56 /// Perform [action] and use [_watch] to measure its runtime (including any |
Harry Terkelsen
2016/05/25 15:51:36
maybe remove the part about _watch in the docs sin
Siggi Cherem (dart-lang)
2016/05/25 16:11:18
good point, done.
| |
63 /// asynchronous callbacks, such as, [Future.then], but excluding code | 57 /// asynchronous callbacks, such as, [Future.then], but excluding code |
64 /// measured by other tasks). | 58 /// measured by other tasks). |
65 measure(action()) => watch == null ? action() : measureZoned(action); | 59 measure(action()) => _isDisabled ? action() : _measureZoned(action); |
66 | 60 |
67 /// Helper method that starts measuring with this [CompilerTask], that is, | 61 /// Helper method that starts measuring with this [CompilerTask], that is, |
68 /// make this task the currently measured task. | 62 /// make this task the currently measured task. |
69 CompilerTask start() { | 63 CompilerTask _start() { |
70 if (watch == null) return null; | 64 if (_isDisabled) return null; |
71 CompilerTask previous = measurer.currentTask; | 65 CompilerTask previous = measurer.currentTask; |
72 measurer.currentTask = this; | 66 measurer.currentTask = this; |
73 if (previous != null) previous.watch.stop(); | 67 if (previous != null) previous._watch.stop(); |
74 // Regardless of whether [previous] is `null` we've returned from the | 68 // Regardless of whether [previous] is `null` we've returned from the |
75 // eventloop. | 69 // eventloop. |
76 measurer.stopAsyncWallClock(); | 70 measurer.stopAsyncWallClock(); |
77 watch.start(); | 71 _watch.start(); |
78 return previous; | 72 return previous; |
79 } | 73 } |
80 | 74 |
81 /// Helper method that stops measuring with this [CompilerTask], that is, | 75 /// Helper method that stops measuring with this [CompilerTask], that is, |
82 /// make [previous] the currently measured task. | 76 /// make [previous] the currently measured task. |
83 void stop(CompilerTask previous) { | 77 void _stop(CompilerTask previous) { |
84 if (watch == null) return; | 78 if (_isDisabled) return; |
85 watch.stop(); | 79 _watch.stop(); |
86 if (previous != null) { | 80 if (previous != null) { |
87 previous.watch.start(); | 81 previous._watch.start(); |
88 } else { | 82 } else { |
89 // If there's no previous task, we're about to return control to the | 83 // If there's no previous task, we're about to return control to the |
90 // event loop. Start counting that as waiting asynchronous I/O. | 84 // event loop. Start counting that as waiting asynchronous I/O. |
91 measurer.startAsyncWallClock(); | 85 measurer.startAsyncWallClock(); |
92 } | 86 } |
93 measurer.currentTask = previous; | 87 measurer.currentTask = previous; |
94 } | 88 } |
95 | 89 |
96 /// Helper method for [measure]. Don't call this method directly as it | 90 _measureZoned(action()) { |
97 /// assumes that [watch] isn't null. | |
98 measureZoned(action()) { | |
99 // Using zones, we're able to track asynchronous operations correctly, as | 91 // Using zones, we're able to track asynchronous operations correctly, as |
100 // our zone will be asked to invoke `then` blocks. Then blocks (the closure | 92 // our zone will be asked to invoke `then` blocks. Then blocks (the closure |
101 // passed to runZoned, and other closures) are run via the `run` functions | 93 // passed to runZoned, and other closures) are run via the `run` functions |
102 // below. | 94 // below. |
103 | 95 |
104 assert(watch != null); | 96 assert(_watch != null); |
105 | 97 |
106 // The current zone is already measuring `this` task. | 98 // The current zone is already measuring `this` task. |
107 if (Zone.current[measurer] == this) return action(); | 99 if (Zone.current[measurer] == this) return action(); |
108 | 100 |
109 /// Run [f] in [zone]. Running must be delegated to [parent] to ensure that | 101 /// Run [f] in [zone]. Running must be delegated to [parent] to ensure that |
110 /// various state is set up correctly (in particular that `Zone.current` | 102 /// various state is set up correctly (in particular that `Zone.current` |
111 /// has the right value). Since [measureZoned] can be called recursively | 103 /// has the right value). Since [_measureZoned] can be called recursively |
112 /// (synchronously), some of the measuring zones we create will be parents | 104 /// (synchronously), some of the measuring zones we create will be parents |
113 /// of other measuring zones, but we still need to call through the parent | 105 /// of other measuring zones, but we still need to call through the parent |
114 /// chain. Consequently, we use a zone value keyed by [measurer] to see if | 106 /// chain. Consequently, we use a zone value keyed by [measurer] to see if |
115 /// we should measure or not when delegating. | 107 /// we should measure or not when delegating. |
116 run(Zone self, ZoneDelegate parent, Zone zone, f()) { | 108 run(Zone self, ZoneDelegate parent, Zone zone, f()) { |
117 if (zone[measurer] != this) return parent.run(zone, f); | 109 if (zone[measurer] != this) return parent.run(zone, f); |
118 CompilerTask previous = start(); | 110 CompilerTask previous = _start(); |
119 try { | 111 try { |
120 return parent.run(zone, f); | 112 return parent.run(zone, f); |
121 } finally { | 113 } finally { |
122 stop(previous); | 114 _stop(previous); |
123 } | 115 } |
124 } | 116 } |
125 | 117 |
126 /// Same as [run] except that [f] takes one argument, [arg]. | 118 /// Same as [run] except that [f] takes one argument, [arg]. |
127 runUnary(Zone self, ZoneDelegate parent, Zone zone, f(arg), arg) { | 119 runUnary(Zone self, ZoneDelegate parent, Zone zone, f(arg), arg) { |
128 if (zone[measurer] != this) return parent.runUnary(zone, f, arg); | 120 if (zone[measurer] != this) return parent.runUnary(zone, f, arg); |
129 CompilerTask previous = start(); | 121 CompilerTask previous = _start(); |
130 try { | 122 try { |
131 return parent.runUnary(zone, f, arg); | 123 return parent.runUnary(zone, f, arg); |
132 } finally { | 124 } finally { |
133 stop(previous); | 125 _stop(previous); |
134 } | 126 } |
135 } | 127 } |
136 | 128 |
137 /// Same as [run] except that [f] takes two arguments ([a1] and [a2]). | 129 /// Same as [run] except that [f] takes two arguments ([a1] and [a2]). |
138 runBinary(Zone self, ZoneDelegate parent, Zone zone, f(a1, a2), a1, a2) { | 130 runBinary(Zone self, ZoneDelegate parent, Zone zone, f(a1, a2), a1, a2) { |
139 if (zone[measurer] != this) return parent.runBinary(zone, f, a1, a2); | 131 if (zone[measurer] != this) return parent.runBinary(zone, f, a1, a2); |
140 CompilerTask previous = start(); | 132 CompilerTask previous = _start(); |
141 try { | 133 try { |
142 return parent.runBinary(zone, f, a1, a2); | 134 return parent.runBinary(zone, f, a1, a2); |
143 } finally { | 135 } finally { |
144 stop(previous); | 136 _stop(previous); |
145 } | 137 } |
146 } | 138 } |
147 | 139 |
148 return runZoned(action, | 140 return runZoned(action, |
149 zoneValues: {measurer: this}, | 141 zoneValues: {measurer: this}, |
150 zoneSpecification: new ZoneSpecification( | 142 zoneSpecification: new ZoneSpecification( |
151 run: run, runUnary: runUnary, runBinary: runBinary)); | 143 run: run, runUnary: runUnary, runBinary: runBinary)); |
152 } | 144 } |
153 | 145 |
154 /// Asynchronous version of [measure]. Use this when action returns a future | 146 /// Asynchronous version of [measure]. Use this when action returns a future |
155 /// that's truly asynchronous, such I/O. Only one task can use this method | 147 /// that's truly asynchronous, such I/O. Only one task can use this method |
156 /// concurrently. | 148 /// concurrently. |
157 /// | 149 /// |
158 /// Note: we assume that this method is used only by the compiler input | 150 /// Note: we assume that this method is used only by the compiler input |
159 /// provider, but it could be used by other tasks as long as the input | 151 /// provider, but it could be used by other tasks as long as the input |
160 /// provider will not be called by those tasks. | 152 /// provider will not be called by those tasks. |
161 measureIo(Future action()) { | 153 measureIo(Future action()) { |
162 return watch == null ? action() : measureIoHelper(action); | 154 if (_isDisabled) return action(); |
163 } | |
164 | 155 |
165 /// Helper method for [measureIo]. Don't call this directly as it assumes | |
166 /// that [watch] isn't null. | |
167 Future measureIoHelper(Future action()) { | |
168 assert(watch != null); | |
169 if (measurer.currentAsyncTask == null) { | 156 if (measurer.currentAsyncTask == null) { |
170 measurer.currentAsyncTask = this; | 157 measurer.currentAsyncTask = this; |
171 } else if (measurer.currentAsyncTask != this) { | 158 } else if (measurer.currentAsyncTask != this) { |
172 throw "Can't track async task '$name' because" | 159 throw "Can't track async task '$name' because" |
173 " '${measurer.currentAsyncTask.name}' is already being tracked."; | 160 " '${measurer.currentAsyncTask.name}' is already being tracked."; |
174 } | 161 } |
175 asyncCount++; | 162 asyncCount++; |
176 return measure(action).whenComplete(() { | 163 return measure(action).whenComplete(() { |
177 asyncCount--; | 164 asyncCount--; |
178 if (asyncCount == 0) measurer.currentAsyncTask = null; | 165 if (asyncCount == 0) measurer.currentAsyncTask = null; |
179 }); | 166 }); |
180 } | 167 } |
181 | 168 |
182 /// Convenience function for combining | |
183 /// [DiagnosticReporter.withCurrentElement] and [measure]. | |
184 measureElement(Element element, action()) { | |
185 return watch == null | |
186 ? reporter.withCurrentElement(element, action) | |
187 : measureElementHelper(element, action); | |
188 } | |
189 | |
190 /// Helper method for [measureElement]. Don't call this directly as it | |
191 /// assumes that [watch] isn't null. | |
192 measureElementHelper(Element element, action()) { | |
193 assert(watch != null); | |
194 return reporter.withCurrentElement(element, () => measure(action)); | |
195 } | |
196 | |
197 /// Measure the time spent in [action] (if in verbose mode) and accumulate it | 169 /// Measure the time spent in [action] (if in verbose mode) and accumulate it |
198 /// under a subtask with the given name. | 170 /// under a subtask with the given name. |
199 measureSubtask(String name, action()) { | 171 measureSubtask(String name, action()) { |
200 return watch == null ? action() : measureSubtaskHelper(name, action); | 172 if (_isDisabled) return action(); |
201 } | |
202 | 173 |
203 /// Helper method for [measureSubtask]. Don't call this directly as it | |
204 /// assumes that [watch] isn't null. | |
205 measureSubtaskHelper(String name, action()) { | |
206 assert(watch != null); | |
207 // Use a nested CompilerTask for the measurement to ensure nested [measure] | 174 // Use a nested CompilerTask for the measurement to ensure nested [measure] |
208 // calls work correctly. The subtasks will never themselves have nested | 175 // calls work correctly. The subtasks will never themselves have nested |
209 // subtasks because they are not accessible outside. | 176 // subtasks because they are not accessible outside. |
210 GenericTask subtask = | 177 GenericTask subtask = |
211 _subtasks.putIfAbsent(name, () => new GenericTask(name, compiler)); | 178 _subtasks.putIfAbsent(name, () => new GenericTask(name, measurer)); |
212 return subtask.measure(action); | 179 return subtask.measure(action); |
213 } | 180 } |
214 | 181 |
215 Iterable<String> get subtasks => _subtasks.keys; | 182 Iterable<String> get subtasks => _subtasks.keys; |
216 | 183 |
217 int getSubtaskTime(String subtask) => _subtasks[subtask].timing; | 184 int getSubtaskTime(String subtask) => _subtasks[subtask].timing; |
218 | 185 |
219 bool getSubtaskIsRunning(String subtask) => _subtasks[subtask].isRunning; | 186 bool getSubtaskIsRunning(String subtask) => _subtasks[subtask].isRunning; |
187 | |
188 /// Used by the dart2js_incremental to provide measurements on each | |
189 /// incremental compile. | |
190 clearMeasurements() { | |
191 if (_isDisabled) return; | |
192 _watch.reset(); | |
193 _subtasks.values.forEach((s) => s.clearMeasurements()); | |
194 } | |
220 } | 195 } |
221 | 196 |
222 class GenericTask extends CompilerTask { | 197 class GenericTask extends CompilerTask { |
223 final String name; | 198 final String name; |
224 | 199 GenericTask(this.name, Measurer measurer) : super(measurer); |
225 GenericTask(this.name, Compiler compiler) : super(compiler); | |
226 } | 200 } |
227 | 201 |
228 class Measurer { | 202 class Measurer { |
229 /// Measures the total runtime from this object was constructed. | 203 /// Measures the total runtime from this object was constructed. |
230 /// | 204 /// |
231 /// Note: MUST be first field to ensure [wallclock] is started before other | 205 /// Note: MUST be the first field of this class to ensure [wallclock] is |
232 /// computations. | 206 /// started before other computations. |
233 final Stopwatch wallClock = new Stopwatch()..start(); | 207 final Stopwatch wallClock = new Stopwatch()..start(); |
234 | 208 |
235 /// Measures gaps between zoned closures due to asynchronicity. | 209 /// Measures gaps between zoned closures due to asynchronicity. |
236 final Stopwatch asyncWallClock = new Stopwatch(); | 210 final Stopwatch asyncWallClock = new Stopwatch(); |
237 | 211 |
212 /// Whether measurement of tasks is enabled. | |
213 final bool enableTaskMeasurements; | |
214 | |
215 Measurer({this.enableTaskMeasurements: false}); | |
216 | |
238 /// The currently running task, that is, the task whose [Stopwatch] is | 217 /// The currently running task, that is, the task whose [Stopwatch] is |
239 /// currently running. | 218 /// currently running. |
240 CompilerTask currentTask; | 219 CompilerTask currentTask; |
241 | 220 |
242 /// The current task which should be charged for asynchronous gaps. | 221 /// The current task which should be charged for asynchronous gaps. |
243 CompilerTask currentAsyncTask; | 222 CompilerTask currentAsyncTask; |
244 | 223 |
245 /// Start counting the total elapsed time since the compiler started. | 224 /// Start counting the total elapsed time since the compiler started. |
246 void startWallClock() { | 225 void startWallClock() { |
247 wallClock.start(); | 226 wallClock.start(); |
248 } | 227 } |
249 | 228 |
250 /// Start counting the total elapsed time since the compiler started. | 229 /// Start counting the total elapsed time since the compiler started. |
251 void stopWallClock() { | 230 void stopWallClock() { |
252 wallClock.stop(); | 231 wallClock.stop(); |
253 } | 232 } |
254 | 233 |
255 /// Call this before returning to the eventloop. | 234 /// Call this before returning to the eventloop. |
256 void startAsyncWallClock() { | 235 void startAsyncWallClock() { |
257 if (currentAsyncTask != null) { | 236 if (currentAsyncTask != null) { |
258 currentAsyncTask.watch.start(); | 237 currentAsyncTask._watch.start(); |
259 } else { | 238 } else { |
260 asyncWallClock.start(); | 239 asyncWallClock.start(); |
261 } | 240 } |
262 } | 241 } |
263 | 242 |
264 /// Call this when the eventloop returns control to us. | 243 /// Call this when the eventloop returns control to us. |
265 void stopAsyncWallClock() { | 244 void stopAsyncWallClock() { |
266 if (currentAsyncTask != null) { | 245 if (currentAsyncTask != null) { |
267 currentAsyncTask.watch.stop(); | 246 currentAsyncTask._watch.stop(); |
268 } | 247 } |
269 asyncWallClock.stop(); | 248 asyncWallClock.stop(); |
270 } | 249 } |
271 } | 250 } |
OLD | NEW |