OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
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. | |
4 | |
5 /// Collects information used to debug and analyze internal parts of the | |
6 /// compiler. | |
7 /// | |
8 /// Currently this is focused mainly on data from types and inference, such as | |
9 /// understanding of types of expressions and precision of send operations. | |
10 /// | |
11 /// This library focuses on representing the information itself. | |
12 /// `stats_builder.dart` contains visitors we use to collect the data, while | |
13 /// `tools/stats/server.dart` contains logic to visualize the data. | |
14 library stats; | |
15 | |
16 /// All results from a single run of the compiler on an application. | |
17 class GlobalResult { | |
18 /// Results grouped by package. | |
19 final Map<String, BundleResult> packages = {}; | |
20 | |
21 /// Results for loose files, typically the entrypoint and files loaded via | |
22 /// relative imports. | |
23 final BundleResult loose = new BundleResult('*loose*'); | |
24 | |
25 /// Results from system libraries (dart:core, dart:async, etc). | |
26 final BundleResult system = new BundleResult('*system*'); | |
27 | |
28 /// Add the result of a library in its corresponding group. | |
29 void add(LibraryResult library) { | |
30 if (library.uri.scheme == 'package') { | |
31 var name = library.uri.pathSegments[0]; | |
32 var package = packages.putIfAbsent(name, () => new BundleResult(name)); | |
33 package.libraries.add(library); | |
34 } else if (library.uri.scheme == 'dart') { | |
35 system.libraries.add(library); | |
36 } else { | |
37 loose.libraries.add(library); | |
38 } | |
39 } | |
40 | |
41 // TODO(sigmund): consider splitting the serialization in multiple files so | |
42 // that not all the information has to be stored in memory at once. | |
43 List toJson() => [] | |
44 ..addAll(packages.values.expand((p) => p.libraries.map((l) => l.toJson()))) | |
45 ..addAll(loose.libraries.map((l) => l.toJson())) | |
46 ..addAll(system.libraries.map((l) => l.toJson())); | |
47 | |
48 static GlobalResult fromJson(List json) { | |
49 var res = new GlobalResult(); | |
50 json.map(LibraryResult.fromJson).forEach(res.add); | |
51 return res; | |
52 } | |
53 | |
54 accept(ResultVisitor v) => v.visitGlobal(this); | |
55 } | |
56 | |
57 /// Summarizes results for a group of libraries. Used by [GlobalResult] to group | |
58 /// the systems libraries, loose libraries, and to create a separate a group per | |
Johnni Winther
2015/07/13 19:21:44
'a separate a group' -> 'a separate group'
Siggi Cherem (dart-lang)
2015/09/29 01:39:34
Ack. (this went away after refactoring)
| |
59 /// package. | |
60 class BundleResult { | |
61 /// Name of the group. | |
62 final String name; | |
63 | |
64 /// Library results that are part of this group. | |
65 final List<LibraryResult> libraries = []; | |
66 | |
67 BundleResult(this.name); | |
68 } | |
69 | |
70 /// Aggregate result for all units in a library. | |
71 class LibraryResult { | |
72 final Uri uri; | |
73 final List<CompilationUnitResults> units = []; | |
74 final List<String> classes = []; | |
Johnni Winther
2015/07/13 19:21:44
What is this used for?
Siggi Cherem (dart-lang)
2015/09/29 01:39:34
it was a placeholder for the future. now that I'm
| |
75 LibraryResult(this.uri); | |
76 accept(ResultVisitor v) => v.visitLibrary(this); | |
77 | |
78 Map toJson() => { | |
79 'uri': '$uri', | |
80 'units': units.map((p) => p.toJson()).toList(), | |
81 }; | |
82 | |
83 static LibraryResult fromJson(Map json) => | |
84 new LibraryResult(Uri.parse(json['uri'])) | |
85 ..units.addAll(json['units'].map(CompilationUnitResult.fromJson)); | |
86 } | |
87 | |
88 /// Results of a compilation unit (library or part). | |
89 class CompilationUnitResult { | |
90 /// Resolved Uri for this unit. | |
91 final Uri uri; | |
92 | |
93 /// Results per function and method in this library | |
94 final List<FunctionResult> functions = []; | |
95 | |
96 accept(ResultVisitor v) => v.visitUnit(this); | |
97 | |
98 CompilationUnitResult(this.uri); | |
99 | |
100 Map toJson() => { | |
101 'uri': '$uri', | |
102 'functions': functions.map((f) => f.toJson()).toList(), | |
103 }; | |
104 | |
105 static CompilationUnitResult fromJson(Map json) => | |
106 new CompilationUnitResult(Uri.parse(json['uri'])) | |
107 ..functions.addAll(json['functions'].map(FunctionResult.fromJson)); | |
108 } | |
109 | |
110 /// Results on a function. | |
111 class FunctionResult { | |
112 /// Name, if-any, of the function. | |
113 final String name; | |
114 | |
115 /// Measurements collected. | |
116 final Measurements measurements; | |
117 | |
118 FunctionResult(this.name, this.measurements); | |
119 accept(ResultVisitor v) => v.visitFunction(this); | |
120 | |
121 Map toJson() => { | |
122 'name' : name, | |
123 'measurements': measurements.toJson(), | |
124 }; | |
125 | |
126 static FunctionResult fromJson(Map json) => | |
127 new FunctionResult(json['name'], | |
128 Measurements.fromJson(json['measurements'])); | |
129 } | |
130 | |
131 /// Top-level set of metrics | |
132 const List<Metric> _topLevelMetrics = const [ | |
133 Metric.functions, | |
134 Metric.send, | |
135 ]; | |
136 | |
137 /// Apply `f` on each metric in DFS order on the metric "tree". | |
138 visitAllMetrics(f) { | |
139 var parentsStack = []; | |
140 helper(Metric m) { | |
141 f(m, parentsStack); | |
142 if (m is GroupedMetric) { | |
143 parentsStack.add(m); | |
144 m.submetrics.forEach(helper); | |
145 parentsStack.removeLast(); | |
146 } | |
147 } | |
148 _topLevelMetrics.forEach(helper); | |
149 } | |
150 | |
151 /// A metric we intend to measure. | |
152 class Metric { | |
153 /// Name for the metric. | |
154 final String name; | |
155 | |
156 const Metric(this.name); | |
157 | |
158 String toString() => name; | |
159 | |
160 /// Total functions in a library/package/program. | |
161 static const Metric functions = const GroupedMetric('functions', const [ | |
162 reachableFunctions, | |
163 ]); | |
164 | |
165 /// Subset of the functions that are reachable. | |
166 static const Metric reachableFunctions = const Metric('reachable functions'); | |
167 | |
168 /// Parent of all send metrics. We classify sends as follows: | |
169 /// | |
170 /// sends | |
171 /// |- monomorphic | |
172 /// | |- static (top-levels, statics) | |
173 /// | |- super | |
174 /// | |- local (access to a local var, call local function) | |
175 /// | |- constructor (like factory ctros) | |
176 /// | |- type variable (reading a type variable) | |
177 /// | |- nsm (known no such method exception) | |
178 /// | |- single-nsm-call (known no such method call, single target) | |
179 /// | |- instance (non-interceptor, only one possible target) | |
180 /// | '- interceptor (interceptor, known) | |
181 /// | | |
182 /// '- polymorphic | |
183 /// |- multi-nsm (known to be nSM, but not sure if error, or call, or | |
184 /// which call) | |
185 /// |- virtual (traditional virtual call, polymorphic equivalent of | |
186 /// | `instance`, no-interceptor) | |
187 /// |- multi-interceptor (1 of n possible interceptors) | |
188 /// '- dynamic (any combination of the above) | |
189 /// | |
190 static const Metric send = const GroupedMetric('send', const [ | |
191 monomorphicSend, | |
192 polymorphicSend, | |
193 ]); | |
194 | |
195 /// Parent of monomorphic sends, see [send] for details. | |
196 static const Metric monomorphicSend = const GroupedMetric('monomorphic', | |
197 const [ | |
198 staticSend, | |
199 superSend, | |
200 localSend, | |
201 constructorSend, | |
202 typeVariableSend, | |
203 nsmErrorSend, | |
204 singleNsmCallSend, | |
205 instanceSend, | |
206 interceptorSend, | |
207 ]); | |
208 | |
209 /// Metric for static calls, see [send] for details. | |
210 static const Metric staticSend = const Metric('static'); | |
211 | |
212 /// Metric for super calls, see [send] for details. | |
213 static const Metric superSend = const Metric('super'); | |
214 | |
215 /// Metric for local variable sends, see [send] for details. | |
216 static const Metric localSend = const Metric('local'); | |
217 | |
218 /// Metric for constructor sends, see [send] for details. | |
219 static const Metric constructorSend = const Metric('constructor'); | |
220 | |
221 /// Metric for type-variable sends, see [send] for details. | |
222 // TODO(sigmund): delete? is mainly associated with compile-time errors | |
223 static const Metric typeVariableSend = const Metric('type variable'); | |
224 | |
225 /// Metric for no-such-method errors, see [send] for details. | |
226 static const Metric nsmErrorSend = const Metric('nSM error'); | |
227 | |
228 /// Metric for calls to noSuchMethod methods with a known target, see [send] | |
229 /// for details. | |
230 static const Metric singleNsmCallSend = const Metric('nSM call single'); | |
231 | |
232 /// Metric for calls to a precisely known instance method, see [send] for | |
233 /// details. | |
234 static const Metric instanceSend = const Metric('instance'); | |
235 | |
236 /// Metric for calls to a precisely known interceptor method, see [send] for | |
237 /// details. | |
238 static const Metric interceptorSend = const Metric('interceptor'); | |
239 | |
240 /// Parent of polymorphic sends, see [send] for details. | |
241 static const Metric polymorphicSend = const GroupedMetric('polymorphic', | |
242 const [ | |
243 multiNsmCallSend, | |
244 virtualSend, | |
245 multiInterceptorSend, | |
246 dynamicSend, | |
247 ]); | |
248 | |
249 /// Metric for calls to noSuchMethod methods with more than one possible | |
250 /// target, see [send] for details. | |
251 static const Metric multiNsmCallSend = const Metric('nSM call multi'); | |
252 | |
253 /// Metric for calls that are dispatched virtually ar runtime, see [send] for | |
254 /// details. | |
255 static const Metric virtualSend = const Metric('virtual'); | |
256 | |
257 /// Metyric for calls to more than one possible interceptor, see [send] for | |
258 /// details. | |
259 static const Metric multiInterceptorSend = const Metric('interceptor multi'); | |
260 | |
261 /// Metyric for dynamic calls for which we know nothing about the target | |
262 /// method. See [send] for details. | |
263 static const Metric dynamicSend = const Metric('dynamic'); | |
264 | |
265 String toJson() => name; | |
266 static Map<String, Metric> _nameToMetricMap = () { | |
267 var res = {}; | |
268 visitAllMetrics((m, _) => res[m.name] = m); | |
269 return res; | |
270 }(); | |
271 | |
272 static Metric fromJson(String name) => _nameToMetricMap[name]; | |
273 } | |
274 | |
275 /// A metric that is subdivided in smaller metrics. | |
276 class GroupedMetric extends Metric { | |
277 final List<Metric> submetrics; | |
278 | |
279 const GroupedMetric(String name, this.submetrics) : super(name); | |
280 } | |
281 | |
282 /// A measurement entry (practically a source-span location where the | |
283 /// measurement was seen). | |
284 class Entry { | |
285 final int begin; | |
286 final int end; | |
287 Entry(this.begin, this.end); | |
288 } | |
289 | |
290 /// A collection of data points for each metric. Used to summarize a single | |
291 /// fucntion, a library, a package, or an entire program. | |
Johnni Winther
2015/07/13 19:21:44
'fucntion' -> 'function'
Siggi Cherem (dart-lang)
2015/09/29 01:39:34
Done.
| |
292 class Measurements { | |
293 final Map<Metric, List<Entry>> entries; | |
294 final Map<Metric, int> counters; | |
295 | |
296 Measurements() | |
297 : entries = <Metric, List<Entry>>{}, | |
298 counters = <Metric, int>{}; | |
299 | |
300 const Measurements.unreachableFunction() | |
301 : counters = const { Metric.functions: 1}, entries = const {}; | |
302 | |
303 Measurements.reachableFunction() | |
304 : counters = { Metric.functions: 1, Metric.reachableFunctions: 1}, | |
305 entries = {}; | |
306 | |
307 /// Record [metric] was seen. The optional [begin] and [end] offsets are | |
308 /// included for metrics that correspond to a source range. Intended to be | |
309 /// used by `StatsBuilder`. | |
310 record(Metric metric, [int begin, int end]) { | |
311 if (begin != null && end != null) { | |
312 entries.putIfAbsent(metric, () => []).add(new Entry(begin, end)); | |
313 } | |
314 counters.putIfAbsent(metric, () => 0); | |
315 counters[metric]++; | |
316 } | |
317 | |
318 /// Removes a previously added entry. Intended only to be used by | |
319 /// `StatsBuilder`. Internally `StatsBuilder` computes redundant information | |
320 /// in order to check for coverage and validate invariants with | |
321 /// [checkInvariant]. This is used to adjust some of the redundant | |
322 /// information. | |
323 popLast(Metric metric) { | |
324 assert(entries[metric] != null && entries[metric].isNotEmpty); | |
325 entries[metric].removeLast(); | |
326 counters[metric]--; | |
327 } | |
328 | |
329 /// Add the counters from [other] into this set of measurements. | |
330 addFrom(Measurements other) { | |
331 other.counters.forEach((metric, value) { | |
332 var current = counters[metric]; | |
333 counters[metric] = current == null ? value : current + value; | |
334 }); | |
335 } | |
336 | |
337 /// Check that every grouped metric totals the individual counts of it's | |
338 /// submetric. | |
339 bool checkInvariant(GroupedMetric key) { | |
340 // TODO(sigmund): use ?? operator. | |
341 int total = counters[key]; | |
342 if (total == null) total = 0; | |
343 int submetricTotal = 0; | |
344 for (var metric in key.submetrics) { | |
345 var n = counters[metric]; | |
346 if (n != null) submetricTotal += n; | |
347 } | |
348 return total == submetricTotal; | |
349 } | |
350 | |
351 Map toJson() { | |
352 var jsonEntries = <String, List<Map>>{}; | |
353 entries.forEach((metric, values) { | |
354 jsonEntries[metric.toJson()] = | |
355 values.map((e) => {'begin': e.begin, 'end': e.end}).toList(); | |
356 }); | |
357 var json = {'entries': jsonEntries}; | |
358 if (counters[Metric.functions] != null) { | |
359 json[Metric.functions.toJson()] = counters[Metric.functions]; | |
360 } | |
361 if (counters[Metric.reachableFunctions] != null) { | |
362 json[Metric.reachableFunctions.toJson()] = | |
363 counters[Metric.reachableFunctions]; | |
364 } | |
365 return json; | |
366 } | |
367 | |
368 static Measurements fromJson(Map json) { | |
369 var res = new Measurements(); | |
370 for (var key in json.keys) { | |
371 var value = json[key]; | |
372 if (value == null) continue; | |
373 if (key == 'entries') { | |
374 value.forEach((metric, jsonEntries) { | |
375 jsonEntries.forEach((v) { | |
376 res.record(Metric.fromJson(metric), v['begin'], v['end']); | |
377 }); | |
378 }); | |
379 } else { | |
380 res.counters[Metric.fromJson(key)] = value; | |
381 } | |
382 } | |
383 return res; | |
384 } | |
385 } | |
386 | |
387 /// Simple visitor of the result hierarchy (useful for computing summaries for | |
388 /// quick reports). | |
389 abstract class ResultVisitor { | |
390 visitGlobal(GlobalResult global); | |
391 visitBundle(BundleResult group); | |
392 visitLibrary(LibraryResult library); | |
393 visitCompilationUnit(CompilationUnitResult unit); | |
394 visitFunction(FunctionResult functino); | |
395 } | |
396 | |
397 /// Recursive visitor that visits every function starting from the global | |
398 /// results. | |
399 abstract class RecursiveResultVisitor extends ResultVisitor { | |
400 visitGlobal(GlobalResult global) { | |
401 global.packages.values.forEach(visitBundle); | |
402 visitBundle(global.system); | |
403 visitBundle(global.loose); | |
404 } | |
405 | |
406 visitBundle(BundleResult group) { | |
407 group.libraries.forEach(visitLibrary); | |
408 } | |
409 | |
410 visitLibrary(LibraryResult library) { | |
411 library.units.forEach(visitCompilationUnit); | |
412 } | |
413 | |
414 visitCompilationUnit(CompilationUnitResult unit) { | |
415 unit.functions.forEach(visitFunction); | |
416 } | |
417 } | |
418 | |
419 /// Color-highighted string used mainly to debug invariants. | |
Johnni Winther
2015/07/13 19:21:44
'highighted' -> 'highlighted'
| |
420 String recursiveDiagnosticString(Measurements measurements, Metric metric) { | |
421 var sb = new StringBuffer(); | |
422 helper(Metric m) { | |
423 int value = measurements.counters[m]; | |
424 if (value == null) value = 0; | |
425 if (m is! GroupedMetric) { | |
426 sb.write(value); | |
427 sb.write(' ${m.name}'); | |
428 return; | |
429 } | |
430 GroupedMetric group = m; | |
431 | |
432 int expected = 0; | |
433 for (var sub in group.submetrics) { | |
434 var n = measurements.counters[sub]; | |
435 if (n != null) expected += n; | |
436 } | |
437 if (value == expected) { | |
438 sb.write('[32m'); | |
Johnni Winther
2015/07/13 19:21:44
Note that colors are not supported on Windows by d
| |
439 sb.write(value); | |
440 } else { | |
441 sb.write('[31m'); | |
442 sb.write(value); | |
443 sb.write('[33m['); | |
444 sb.write(expected); | |
445 sb.write(']'); | |
446 } | |
447 sb.write('[0m'); | |
448 sb.write(' ${group.name}'); | |
449 | |
450 bool first = true; | |
451 sb.write('('); | |
452 for (var sub in group.submetrics) { | |
453 if (first) { | |
454 first = false; | |
455 } else { | |
456 sb.write(' + '); | |
457 } | |
458 helper(sub); | |
459 } | |
460 sb.write(')'); | |
461 } | |
462 helper(metric); | |
463 return sb.toString(); | |
464 } | |
OLD | NEW |