OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, the Dartino 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.md file. | |
4 | |
5 library fletchc.program_info; | |
6 | |
7 import 'dart:async' show | |
8 Future, | |
9 Stream, | |
10 StreamSink; | |
11 | |
12 import 'dart:io' as io; | |
13 | |
14 import 'dart:io' show | |
15 BytesBuilder; | |
16 | |
17 import 'dart:convert' show | |
18 JSON, | |
19 LineSplitter, | |
20 UTF8; | |
21 | |
22 import 'dart:typed_data' show | |
23 Int32List, | |
24 Uint8List, | |
25 ByteData, | |
26 Endianness; | |
27 | |
28 import 'package:persistent/persistent.dart' show | |
29 Pair; | |
30 | |
31 import 'vm_commands.dart' show | |
32 WriteSnapshotResult; | |
33 | |
34 import 'fletch_system.dart' show | |
35 FletchClass, | |
36 FletchFunction, | |
37 FletchSystem; | |
38 | |
39 import 'src/fletch_selector.dart' show | |
40 FletchSelector; | |
41 | |
42 enum Configuration { | |
43 Offset64BitsDouble, | |
44 Offset64BitsFloat, | |
45 Offset32BitsDouble, | |
46 Offset32BitsFloat, | |
47 } | |
48 | |
49 class ProgramInfo { | |
50 final List<String> _strings; | |
51 | |
52 // Maps selector-id -> string-id | |
53 final List<int> _selectorNames; | |
54 | |
55 // Maps configuration -> offset -> string-id | |
56 final Map<Configuration, Map<int, int>> _classNames; | |
57 | |
58 // Maps configuration -> offset -> string-id | |
59 final Map<Configuration, Map<int, int>> _functionNames; | |
60 | |
61 // Snapshot hashtag for validation. | |
62 final hashtag; | |
63 | |
64 ProgramInfo(this._strings, this._selectorNames, | |
65 this._classNames, this._functionNames, | |
66 this.hashtag); | |
67 | |
68 String classNameOfFunction(Configuration conf, int functionOffset) { | |
69 return _getString(_classNames[conf][functionOffset]); | |
70 } | |
71 | |
72 String functionName(Configuration conf, int functionOffset) { | |
73 return _getString(_functionNames[conf][functionOffset]); | |
74 } | |
75 | |
76 String className(Configuration conf, int classOffset) { | |
77 return _getString(_classNames[conf][classOffset]); | |
78 } | |
79 | |
80 String selectorName(FletchSelector selector) { | |
81 return _getString(_selectorNames[selector.id]); | |
82 } | |
83 | |
84 String _getString(int stringId) { | |
85 String name = null; | |
86 if (stringId != null && stringId != -1) { | |
87 name = _strings[stringId]; | |
88 if (name == '') name = null; | |
89 } | |
90 return name; | |
91 } | |
92 } | |
93 | |
94 abstract class ProgramInfoJson { | |
95 static String encode(ProgramInfo info, {List<Configuration> enabledConfigs}) { | |
96 if (enabledConfigs == null) { | |
97 enabledConfigs = Configuration.values; | |
98 } | |
99 | |
100 Map<String, List<int>> buildTables( | |
101 Map<Configuration, Map<int, int>> offset2stringIds) { | |
102 | |
103 List<int> convertMap(Configuration conf) { | |
104 if (enabledConfigs.contains(conf)) { | |
105 var map = offset2stringIds[conf]; | |
106 List<int> list = new List<int>(map.length * 2); | |
107 int offset = 0; | |
108 map.forEach((int a, int b) { | |
109 list[offset++] = a; | |
110 list[offset++] = b; | |
111 }); | |
112 return list; | |
113 } else { | |
114 return const []; | |
115 } | |
116 } | |
117 | |
118 return { | |
119 'b64double': convertMap(Configuration.Offset64BitsDouble), | |
120 'b64float': convertMap(Configuration.Offset64BitsFloat), | |
121 'b32double': convertMap(Configuration.Offset32BitsDouble), | |
122 'b32float': convertMap(Configuration.Offset32BitsFloat), | |
123 }; | |
124 } | |
125 | |
126 return JSON.encode({ | |
127 'strings': info._strings, | |
128 'selectors': info._selectorNames, | |
129 'class-names': buildTables(info._classNames), | |
130 'function-names' : buildTables(info._functionNames), | |
131 'hashtag': info.hashtag, | |
132 }); | |
133 } | |
134 | |
135 static ProgramInfo decode(String string) { | |
136 var json = JSON.decode(string); | |
137 | |
138 Map<int, int> convertList(List<int> list) { | |
139 Map<int, int> map = {}; | |
140 for (int i = 0; i < list.length; i += 2) { | |
141 map[list[i]] = list[i + 1]; | |
142 } | |
143 return map; | |
144 } | |
145 | |
146 var classNames = { | |
147 Configuration.Offset64BitsDouble : | |
148 convertList(json['class-names']['b64double']), | |
149 Configuration.Offset64BitsFloat: | |
150 convertList(json['class-names']['b64float']), | |
151 Configuration.Offset32BitsDouble : | |
152 convertList(json['class-names']['b32double']), | |
153 Configuration.Offset32BitsFloat : | |
154 convertList(json['class-names']['b32float']), | |
155 }; | |
156 | |
157 var functionNames = { | |
158 Configuration.Offset64BitsDouble : | |
159 convertList(json['function-names']['b64double']), | |
160 Configuration.Offset64BitsFloat: | |
161 convertList(json['function-names']['b64float']), | |
162 Configuration.Offset32BitsDouble : | |
163 convertList(json['function-names']['b32double']), | |
164 Configuration.Offset32BitsFloat : | |
165 convertList(json['function-names']['b32float']), | |
166 }; | |
167 | |
168 return new ProgramInfo( | |
169 json['strings'], json['selectors'], | |
170 classNames, functionNames, | |
171 json['hashtag']); | |
172 } | |
173 } | |
174 | |
175 abstract class ProgramInfoBinary { | |
176 static const int _INVALID_INDEX = 0xffffff; | |
177 static final int _HEADER_LENGTH = 4 * (2 + 2 * Configuration.values.length); | |
178 | |
179 static List<int> encode(ProgramInfo info, | |
180 {List<Configuration> enabledConfigs}) { | |
181 if (enabledConfigs == null) { | |
182 enabledConfigs = Configuration.values; | |
183 } | |
184 | |
185 List<int> stringOffsetsInStringTable = []; | |
186 | |
187 void ensureEncodableAs24Bit(int number) { | |
188 if (number >= ((1 << 24) - 1)) { | |
189 throw new Exception( | |
190 "The binary program information format cannot encode offsets " | |
191 "larger than 16 MB (24 bits) at the moment."); | |
192 } | |
193 } | |
194 | |
195 List<int> buildStringTable() { | |
196 BytesBuilder builder = new BytesBuilder(); | |
197 for (int i = 0; i < info._strings.length; i++) { | |
198 stringOffsetsInStringTable.add(builder.length); | |
199 String name = info._strings[i]; | |
200 if (name != null) { | |
201 builder.add(UTF8.encode(name)); | |
202 } | |
203 builder.addByte(0); | |
204 } | |
205 return builder.takeBytes(); | |
206 } | |
207 | |
208 List<int> buildSelectorTable(List<int> stringIds) { | |
209 Uint8List bytes = new Uint8List(3 * stringIds.length); | |
210 | |
211 int offset = 0; | |
212 | |
213 void writeByte(int byte) { | |
214 bytes[offset++] = byte; | |
215 } | |
216 | |
217 void writeNumber(int number) { | |
218 ensureEncodableAs24Bit(number); | |
219 | |
220 // 0xffffff means -1 | |
221 if (number == -1) number = _INVALID_INDEX; | |
222 | |
223 writeByte(number & 0xff); | |
224 writeByte((number >> 8) & 0xff); | |
225 writeByte((number >> 16) & 0xff); | |
226 } | |
227 | |
228 // We write tuples [program-offset, string-table-offset]. | |
229 // So the user can use binary search using a program offset to find the | |
230 // string offset. | |
231 for (int i = 0; i < stringIds.length; i++) { | |
232 int stringId = stringIds[i]; | |
233 | |
234 if (stringId != -1) { | |
235 int stringOffset = stringOffsetsInStringTable[stringId]; | |
236 writeNumber(stringOffset); | |
237 } else { | |
238 writeNumber(-1); | |
239 } | |
240 } | |
241 | |
242 assert(offset == bytes.length); | |
243 | |
244 return bytes; | |
245 } | |
246 | |
247 List<int> buildOffsetTable(Map<int, int> map) { | |
248 Uint8List bytes = new Uint8List(2 * 3 * map.length); | |
249 | |
250 int offset = 0; | |
251 | |
252 void writeByte(int byte) { | |
253 bytes[offset++] = byte; | |
254 } | |
255 | |
256 void writeNumber(int number) { | |
257 // 0xffffff means -1 | |
258 ensureEncodableAs24Bit(number); | |
259 | |
260 if (number == -1) number = _INVALID_INDEX; | |
261 | |
262 writeByte(number & 0xff); | |
263 writeByte((number >> 8) & 0xff); | |
264 writeByte((number >> 16) & 0xff); | |
265 } | |
266 | |
267 // We write tuples [program-offset, string-table-offset]. | |
268 // So the user can use binary search using a program offset to find the | |
269 // string offset. | |
270 List<int> offsets = map.keys.toList()..sort(); | |
271 for (int i = 0; i < offsets.length; i++) { | |
272 int offset = offsets[i]; | |
273 int stringId = map[offset]; | |
274 | |
275 // We only make an entry for [offset] if there is a known string. | |
276 if (stringId != -1) { | |
277 int stringOffset = stringOffsetsInStringTable[stringId]; | |
278 writeNumber(offset); | |
279 writeNumber(stringOffset); | |
280 } | |
281 } | |
282 | |
283 return new Uint8List.view(bytes.buffer, 0, offset); | |
284 } | |
285 | |
286 List<List<int>> tables = []; | |
287 tables.add(buildStringTable()); | |
288 tables.add(buildSelectorTable(info._selectorNames)); | |
289 for (Configuration conf in Configuration.values) { | |
290 if (enabledConfigs.contains(conf)) { | |
291 List<int> offsetTable = buildOffsetTable(info._classNames[conf]); | |
292 tables.add(offsetTable); | |
293 } else { | |
294 tables.add([]); | |
295 } | |
296 } | |
297 for (Configuration conf in Configuration.values) { | |
298 if (enabledConfigs.contains(conf)) { | |
299 List<int> offsetTable = buildOffsetTable(info._functionNames[conf]); | |
300 tables.add(offsetTable); | |
301 } else { | |
302 tables.add([]); | |
303 } | |
304 } | |
305 | |
306 int tableLengths = tables.map((t) => t.length).fold(0, (a, b) => a + b); | |
307 int length = _HEADER_LENGTH + tableLengths; | |
308 | |
309 Uint8List bytes = new Uint8List(length); | |
310 ByteData header = new ByteData.view(bytes.buffer); | |
311 | |
312 int offset = 0; | |
313 writeLength(List<int> data) { | |
314 header.setUint32(offset, data.length, Endianness.LITTLE_ENDIAN); | |
315 offset += 4; | |
316 } | |
317 writeTable(List<int> data) { | |
318 bytes.setRange(offset, offset + data.length, data); | |
319 offset += data.length; | |
320 } | |
321 | |
322 tables.forEach(writeLength); | |
323 assert(offset == _HEADER_LENGTH); | |
324 tables.forEach(writeTable); | |
325 | |
326 return bytes; | |
327 } | |
328 | |
329 static ProgramInfo decode(List<int> data) { | |
330 Uint8List bytes = new Uint8List.fromList(data); | |
331 ByteData header = new ByteData.view(bytes.buffer); | |
332 | |
333 int offset = 0; | |
334 int readLength() { | |
335 int length = header.getUint32(offset, Endianness.LITTLE_ENDIAN); | |
336 offset += 4; | |
337 return length; | |
338 } | |
339 List<int> readTable(int length) { | |
340 var view = new Uint8List.view(bytes.buffer, offset, length); | |
341 offset += length; | |
342 return view; | |
343 } | |
344 | |
345 Map<int, int> stringOffsetToIndex = {}; | |
346 List<String> buildStringDecodingTable(List<int> data) { | |
347 List<String> strings = []; | |
348 int start = 0; | |
349 while (start < data.length) { | |
350 stringOffsetToIndex[start] = strings.length; | |
351 | |
352 int end = start; | |
353 while (data[end] != 0) end++; | |
354 | |
355 strings.add(UTF8.decode(data.sublist(start, end))); | |
356 start = end + 1; | |
357 } | |
358 return strings; | |
359 } | |
360 | |
361 List<int> decodeSelectors(List<int> data) { | |
362 assert(data.length % 3 == 0); | |
363 List<int> indices = new List(data.length ~/ 3); | |
364 for (int offset = 0; offset < data.length; offset += 3) { | |
365 int number = | |
366 data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16); | |
367 if (number == _INVALID_INDEX) number = -1; | |
368 if (number != -1) number = stringOffsetToIndex[number]; | |
369 indices[offset ~/ 3] = number; | |
370 } | |
371 return indices; | |
372 } | |
373 | |
374 Map<int, int> decodeTable(List<int> data) { | |
375 assert(data.length % 6 == 0); | |
376 Map<int, int> offset2stringId = {}; | |
377 for (int offset = 0; offset < data.length; offset += 6) { | |
378 int programOffset = | |
379 data[offset] | | |
380 (data[offset + 1] << 8) | | |
381 (data[offset + 2] << 16); | |
382 int stringOffset = | |
383 data[offset + 3] | | |
384 (data[offset + 4] << 8) | | |
385 (data[offset + 5] << 16); | |
386 | |
387 if (programOffset != _INVALID_INDEX && stringOffset != _INVALID_INDEX) { | |
388 offset2stringId[programOffset] = stringOffsetToIndex[stringOffset]; | |
389 } else { | |
390 offset2stringId[programOffset] = -1; | |
391 } | |
392 } | |
393 return offset2stringId; | |
394 } | |
395 | |
396 int stringTableLength = readLength(); | |
397 int selectorTableLength = readLength(); | |
398 | |
399 Map<Configuration, int> classTableLengths = {}; | |
400 Configuration.values.forEach( | |
401 (conf) => classTableLengths[conf] = readLength()); | |
402 | |
403 Map<Configuration, int> functionTableLengths = {}; | |
404 Configuration.values.forEach( | |
405 (conf) => functionTableLengths[conf] = readLength()); | |
406 | |
407 List<int> stringTable = readTable(stringTableLength); | |
408 | |
409 List<String> strings = buildStringDecodingTable(stringTable); | |
410 List<int> selectorTable = decodeSelectors(readTable(selectorTableLength)); | |
411 | |
412 Map<Configuration, Map<int, int>> classNames = {}; | |
413 Configuration.values.forEach((conf) { | |
414 classNames[conf] = decodeTable(readTable(classTableLengths[conf])); | |
415 }); | |
416 | |
417 Map<Configuration, Map<int, int>> functionNames = {}; | |
418 Configuration.values.forEach((conf) { | |
419 functionNames[conf] = decodeTable(readTable(functionTableLengths[conf])); | |
420 }); | |
421 | |
422 assert(offset == (_HEADER_LENGTH + | |
423 stringTableLength + | |
424 selectorTableLength + | |
425 classTableLengths.values.fold(0, (a, b) => a + b) + | |
426 functionTableLengths.values.fold(0, (a, b) => a + b))); | |
427 | |
428 return new ProgramInfo( | |
429 strings, | |
430 selectorTable, | |
431 classNames, | |
432 functionNames, | |
433 0); // hashtag for shapshot is not supported for binary format. | |
434 } | |
435 } | |
436 | |
437 ProgramInfo buildProgramInfo(FletchSystem system, WriteSnapshotResult result) { | |
438 List<String> strings = []; | |
439 Map<String, int> stringIndices = {}; | |
440 List<int> selectors = []; | |
441 | |
442 int newName(String name) { | |
443 if (name == null) return -1; | |
444 | |
445 var index = stringIndices[name]; | |
446 if (index == null) { | |
447 index = strings.length; | |
448 strings.add(name); | |
449 stringIndices[name] = index; | |
450 } | |
451 return index; | |
452 } | |
453 | |
454 void setIndex(List<int> list, int index, value) { | |
455 while (list.length <= index) { | |
456 list.add(-1); | |
457 } | |
458 list[index] = value; | |
459 } | |
460 | |
461 system.symbolByFletchSelectorId.forEach((Pair<int, String> pair) { | |
462 setIndex(selectors, pair.fst, newName(pair.snd)); | |
463 }); | |
464 | |
465 Map<int, FletchClass> functionId2Class = {}; | |
466 system.classesById.forEach((Pair<int, FletchClass> pair) { | |
467 FletchClass klass = pair.snd; | |
468 klass.methodTable.forEach((Pair<int, int> pair) { | |
469 int functionId = pair.snd; | |
470 functionId2Class[functionId] = klass; | |
471 }); | |
472 }); | |
473 | |
474 Map<Configuration, Map<int, int>> newTable() { | |
475 return <Configuration, Map<int, int>>{ | |
476 Configuration.Offset64BitsDouble : <int,int>{}, | |
477 Configuration.Offset64BitsFloat : <int,int>{}, | |
478 Configuration.Offset32BitsDouble : <int,int>{}, | |
479 Configuration.Offset32BitsFloat : <int,int>{}, | |
480 }; | |
481 } | |
482 | |
483 fillTable(Map<Configuration, Map<int, int>> dst, | |
484 Int32List list, | |
485 String symbol(int id)) { | |
486 for (int offset = 0; offset < list.length; offset += 5) { | |
487 int id = list[offset + 0]; | |
488 int stringId = newName(symbol(id)); | |
489 | |
490 if (stringId != -1) { | |
491 dst[Configuration.Offset64BitsDouble][list[offset + 1]] = stringId; | |
492 dst[Configuration.Offset64BitsFloat][list[offset + 2]] = stringId; | |
493 dst[Configuration.Offset32BitsDouble][list[offset + 3]] = stringId; | |
494 dst[Configuration.Offset32BitsFloat][list[offset + 4]] = stringId; | |
495 } | |
496 } | |
497 } | |
498 | |
499 var functionNames = newTable(); | |
500 var classNames = newTable(); | |
501 | |
502 fillTable(functionNames, | |
503 result.functionOffsetTable, | |
504 (id) => system.functionsById[id].name); | |
505 fillTable(classNames, | |
506 result.classOffsetTable, | |
507 (id) { | |
508 FletchClass klass = system.classesById[id]; | |
509 if (klass == null) { | |
510 // Why do we get here? | |
511 return null; | |
512 } | |
513 return klass.name; | |
514 }); | |
515 fillTable(classNames, | |
516 result.functionOffsetTable, | |
517 (id) { | |
518 FletchClass klass = functionId2Class[id]; | |
519 if (klass != null) return klass.name; | |
520 return null; | |
521 }); | |
522 | |
523 return new ProgramInfo(strings, selectors, classNames, | |
524 functionNames, result.hashtag); | |
525 } | |
526 | |
527 final RegExp _FrameRegexp = | |
528 new RegExp(r'^Frame +([0-9]+): Function\(([0-9]+)\)$'); | |
529 | |
530 final RegExp _NSMRegexp = | |
531 new RegExp(r'^NoSuchMethodError\(([0-9]+), ([0-9]+)\)$'); | |
532 | |
533 Stream<String> decodeStackFrames(Configuration conf, | |
534 ProgramInfo info, | |
535 Stream<String> input) async* { | |
536 await for (String line in input) { | |
537 Match frameMatch = _FrameRegexp.firstMatch(line); | |
538 Match nsmMatch = _NSMRegexp.firstMatch(line); | |
539 if (frameMatch != null) { | |
540 String frameNr = frameMatch.group(1); | |
541 int functionOffset = int.parse(frameMatch.group(2)); | |
542 | |
543 String className = info.classNameOfFunction(conf, functionOffset); | |
544 String functionName = info.functionName(conf, functionOffset); | |
545 | |
546 if (className == null) { | |
547 yield ' $frameNr: $functionName\n'; | |
548 } else { | |
549 yield ' $frameNr: $className.$functionName\n'; | |
550 } | |
551 } else if (nsmMatch != null) { | |
552 int classOffset = int.parse(nsmMatch.group(1)); | |
553 FletchSelector selector = | |
554 new FletchSelector(int.parse(nsmMatch.group(2))); | |
555 String functionName = info.selectorName(selector); | |
556 String className = info.className(conf, classOffset); | |
557 | |
558 if (className != null && functionName != null) { | |
559 yield 'NoSuchMethodError: $className.$functionName\n'; | |
560 } else if (functionName != null) { | |
561 yield 'NoSuchMethodError: $functionName\n'; | |
562 } else { | |
563 yield 'NoSuchMethodError: <unknown method>\n'; | |
564 } | |
565 } else { | |
566 yield '$line\n'; | |
567 } | |
568 } | |
569 } | |
570 | |
571 Future<int> decodeProgramMain( | |
572 List<String> arguments, | |
573 Stream<List<int>> input, | |
574 StreamSink<List<int>> output) async { | |
575 | |
576 usage(message) { | |
577 print("Invalid arguments: $message"); | |
578 print("Usage: ${io.Platform.script} " | |
579 "<32/64> <float/double> <snapshot.info.{json/bin}>"); | |
580 } | |
581 | |
582 if (arguments.length != 3) { | |
583 usage("Exactly 3 arguments must be supplied"); | |
584 return 1; | |
585 } | |
586 | |
587 String bits = arguments[0]; | |
588 if (!['32', '64'].contains(bits)) { | |
589 usage("Bit width must be 32 or 64."); | |
590 return 1; | |
591 } | |
592 | |
593 String floatOrDouble = arguments[1]; | |
594 if (!['float', 'double'].contains(floatOrDouble)) { | |
595 usage("Floating point argument must be 'float' or 'double'."); | |
596 return 1; | |
597 } | |
598 | |
599 String filename = arguments[2]; | |
600 bool isJsonFile = filename.endsWith('.json'); | |
601 bool isBinFile = filename.endsWith('.bin'); | |
602 if (!isJsonFile && !isBinFile) { | |
603 usage("The program info file must end in '.bin' or '.json' " | |
604 "(was: '$filename')."); | |
605 return 1; | |
606 } | |
607 | |
608 io.File file = new io.File(filename); | |
609 if (!await file.exists()) { | |
610 usage("The file '$filename' does not exist."); | |
611 return 1; | |
612 } | |
613 | |
614 ProgramInfo info; | |
615 | |
616 if (isJsonFile) { | |
617 info = ProgramInfoJson.decode(await file.readAsString()); | |
618 } else { | |
619 info = ProgramInfoBinary.decode(await file.readAsBytes()); | |
620 } | |
621 | |
622 Stream<String> inputLines = | |
623 input.transform(UTF8.decoder).transform(new LineSplitter()); | |
624 | |
625 Configuration conf = _getConfiguration(bits, floatOrDouble); | |
626 Stream<String> decodedFrames = decodeStackFrames(conf, info, inputLines); | |
627 await decodedFrames.transform(UTF8.encoder).pipe(output); | |
628 | |
629 return 0; | |
630 } | |
631 | |
632 | |
633 // We are only interested in two kind of lines in the fletch.ticks file. | |
634 final RegExp tickRegexp = | |
635 new RegExp(r'^0x([0-9a-f]+),0x([0-9a-f]+),0x([0-9a-f]+)'); | |
636 final RegExp propertyRegexp = new RegExp(r'^(\w+)=(.*$)'); | |
637 | |
638 // Tick contains information from a line matching tickRegexp. | |
639 class Tick { | |
640 final int pc; // The actual program counter where the tick occurred. | |
641 final int bcp; // The bytecode pointer relative to program heap start. | |
642 final int hashtag; | |
643 Tick(this.pc, this.bcp,this.hashtag); | |
644 } | |
645 | |
646 // Property contains information from a line matching propertyRegexp. | |
647 class Property { | |
648 final String name; | |
649 final String value; | |
650 Property(this.name, this.value); | |
651 } | |
652 | |
653 // FunctionInfo captures profiler information for a function. | |
654 class FunctionInfo { | |
655 int ticks = 0; // Accumulated number of ticks. | |
656 final String name; // Name that indentifies the function. | |
657 | |
658 FunctionInfo(this.name); | |
659 | |
660 int Percent(int total_ticks) => ticks * 100 ~/ total_ticks; | |
661 | |
662 void Print(int total_ticks) { | |
663 print(" -${Percent(total_ticks).toString().padLeft(3, ' ')}% $name"); | |
664 } | |
665 | |
666 static String ComputeName(String function_name, String class_name) { | |
667 if (class_name == null) return function_name; | |
668 return "$class_name.$function_name"; | |
669 } | |
670 } | |
671 | |
672 Stream decode(Stream<List<int>> input) async* { | |
673 Stream<String> inputLines = | |
674 input.transform(UTF8.decoder).transform(new LineSplitter()); | |
675 await for (String line in inputLines) { | |
676 Match t = tickRegexp.firstMatch(line); | |
677 if (t != null) { | |
678 int pc = int.parse(t.group(1), radix: 16); | |
679 int offset = 0; | |
680 int hashtag = 0; | |
681 if (t.groupCount > 1) { | |
682 offset = int.parse(t.group(2), radix: 16); | |
683 hashtag = int.parse(t.group(3), radix: 16); | |
684 } | |
685 yield new Tick(pc, offset, hashtag); | |
686 } else { | |
687 t = propertyRegexp.firstMatch(line); | |
688 if (t != null) yield new Property(t.group(1), t.group(2)); | |
689 } | |
690 } | |
691 } | |
692 | |
693 // Binary search for named entry start. | |
694 NamedEntry findEntry(List<NamedEntry> functions, Tick t) { | |
695 int low = 0; | |
696 int high = functions.length - 1; | |
697 while (low + 1 < high) { | |
698 int i = low + ((high - low) ~/ 2); | |
699 NamedEntry current = functions[i]; | |
700 if (current.offset < t.bcp) { | |
701 low = i; | |
702 } else { | |
703 high = i; | |
704 } | |
705 } | |
706 return functions[low]; | |
707 } | |
708 | |
709 // NamedEntry captures a named entry caputred in the .info.json file. | |
710 class NamedEntry { | |
711 final int offset; | |
712 final String name; | |
713 NamedEntry(this.offset, this.name); | |
714 } | |
715 | |
716 class Profile { | |
717 final String sample_filename; | |
718 final String info_filename; | |
719 Profile(this.sample_filename,this.info_filename); | |
720 | |
721 // Tick information. | |
722 int total_ticks = 0; | |
723 int runtime_ticks = 0; | |
724 int interpreter_ticks = 0; | |
725 int discarded_ticks = 0; | |
726 int other_snapshot_ticks = 0; | |
727 | |
728 // Memory model. | |
729 String model; | |
730 | |
731 // All the ticks. | |
732 List<Tick> ticks = <Tick>[]; | |
733 | |
734 // The resulting histogram. | |
735 List<FunctionInfo> histogram; | |
736 | |
737 void Print() { | |
738 print("# Tick based profiler result."); | |
739 | |
740 for (FunctionInfo func in histogram) { | |
741 if (func.Percent(total_ticks) < 2) break; | |
742 func.Print(total_ticks); | |
743 } | |
744 | |
745 print("# ticks in interpreter=${interpreter_ticks}"); | |
746 if (runtime_ticks > 0) print(" runtime=${runtime_ticks}"); | |
747 if (discarded_ticks > 0) print(" discarded=${discarded_ticks}"); | |
748 if (other_snapshot_ticks> 0) { | |
749 print(" other_snapshot=${other_snapshot_ticks}"); | |
750 } | |
751 } | |
752 } | |
753 | |
754 Future<Profile> decodeTickSamples( | |
755 List<String> arguments, | |
756 Stream<List<int>> input, | |
757 StreamSink<List<int>> output) async { | |
758 | |
759 usage(message) { | |
760 print("Invalid arguments: $message"); | |
761 print("Usage: ${io.Platform.script} <fletch.ticks> <snapshot.info.json>"); | |
762 } | |
763 | |
764 if (arguments.length != 2) { | |
765 usage("Exactly 2 arguments must be supplied"); | |
766 return null; | |
767 } | |
768 | |
769 String sample_filename = arguments[0]; | |
770 io.File sample_file = new io.File(sample_filename); | |
771 if (!await sample_file.exists()) { | |
772 usage("The file '$sample_filename' does not exist."); | |
773 return null; | |
774 } | |
775 | |
776 String info_filename = arguments[1]; | |
777 if (!info_filename.endsWith('.info.json')) { | |
778 usage("The program info file must end in '.info.json' " | |
779 "(was: '$info_filename')."); | |
780 return null; | |
781 } | |
782 | |
783 io.File info_file = new io.File(info_filename); | |
784 if (!await info_file.exists()) { | |
785 usage("The file '$info_filename' does not exist."); | |
786 return null; | |
787 } | |
788 | |
789 ProgramInfo info = ProgramInfoJson.decode(await info_file.readAsString()); | |
790 Profile profile = new Profile(sample_filename, info_filename); | |
791 | |
792 // Process the tick sample file. | |
793 await for (var t in decode(sample_file.openRead())) { | |
794 if (t is Tick) { | |
795 profile.ticks.add(t); | |
796 } else if (t is Property) { | |
797 if (t.name == 'discarded') profile.discarded_ticks = int.parse(t.value); | |
798 if (t.name == 'model') profile.model = t.value; | |
799 } | |
800 } | |
801 if (profile.model == null) { | |
802 print("Memory model absent in sample file."); | |
803 return null; | |
804 } | |
805 | |
806 // Compute the configuration key based on the memory model. | |
807 Configuration conf; | |
808 String model = profile.model; | |
809 if (model == 'b64double') { | |
810 conf = Configuration.Offset64BitsDouble; | |
811 } else if (model == 'b64float') { | |
812 conf = Configuration.Offset64BitsFloat; | |
813 } else if (model == 'b32double') { | |
814 conf = Configuration.Offset32BitsDouble; | |
815 } else if (model == 'b32float') { | |
816 conf = Configuration.Offset32BitsFloat; | |
817 } else { | |
818 print("Memory model in sample file ${model} cannot be recognized."); | |
819 return null; | |
820 } | |
821 | |
822 // Compute a offset sorted list of Function entries. | |
823 List<NamedEntry> functions = new List<NamedEntry>(); | |
824 Map<int,int> fnames = info._functionNames[conf]; | |
825 fnames.forEach((key, value) { | |
826 functions.add(new NamedEntry(key, info._getString(value))); | |
827 }); | |
828 functions.sort((a, b) => a.offset - b.offset); | |
829 | |
830 // Compute a offset sorted list of Class entries. | |
831 List<NamedEntry> classes = new List<NamedEntry>(); | |
832 Map<int,int> cnames = info._classNames[conf]; | |
833 cnames.forEach((key, value) { | |
834 classes.add(new NamedEntry(key, info._getString(value))); | |
835 }); | |
836 classes.sort((a, b) => a.offset - b.offset); | |
837 | |
838 Map<String,FunctionInfo> results = <String,FunctionInfo>{}; | |
839 for (Tick t in profile.ticks) { | |
840 profile.total_ticks++; | |
841 if (t.bcp == 0) { | |
842 profile.runtime_ticks++; | |
843 } else if (t.hashtag != info.hashtag) { | |
844 profile.other_snapshot_ticks++; | |
845 } else { | |
846 profile.interpreter_ticks++; | |
847 NamedEntry fe = findEntry(functions, t); | |
848 if (fe?.name != null) { | |
849 NamedEntry ce = findEntry(classes, t); | |
850 String key = FunctionInfo.ComputeName(fe.name, ce?.name); | |
851 FunctionInfo f = | |
852 results.putIfAbsent(key, () => new FunctionInfo(key)); | |
853 f.ticks++; | |
854 } | |
855 } | |
856 } | |
857 | |
858 // Sort the values into the histogram. | |
859 List<FunctionInfo> histogram = | |
860 new List<FunctionInfo>.from(results.values); | |
861 histogram.sort((a,b) { return b.ticks - a.ticks; }); | |
862 profile.histogram = histogram; | |
863 | |
864 return profile; | |
865 } | |
866 | |
867 Configuration _getConfiguration(bits, floatOrDouble) { | |
868 if (bits == '64') { | |
869 if (floatOrDouble == 'float') return Configuration.Offset64BitsFloat; | |
870 else if (floatOrDouble == 'double') return Configuration.Offset64BitsDouble; | |
871 } else if (bits == '32') { | |
872 if (floatOrDouble == 'float') return Configuration.Offset32BitsFloat; | |
873 else if (floatOrDouble == 'double') return Configuration.Offset32BitsDouble; | |
874 } | |
875 throw 'Invalid arguments'; | |
876 } | |
OLD | NEW |