Chromium Code Reviews| 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: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 } | |
| OLD | NEW |