| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| library vm_snapshot_analysis.utils; |
| |
| import 'package:vm_snapshot_analysis/ascii_table.dart'; |
| import 'package:vm_snapshot_analysis/program_info.dart'; |
| import 'package:vm_snapshot_analysis/instruction_sizes.dart' |
| as instruction_sizes; |
| import 'package:vm_snapshot_analysis/treemap.dart'; |
| import 'package:vm_snapshot_analysis/v8_profile.dart' as v8_profile; |
| |
| ProgramInfo loadProgramInfoFromJson(Object json, |
| {bool collapseAnonymousClosures = false}) { |
| if (v8_profile.Snapshot.isV8HeapSnapshot(json)) { |
| return v8_profile.toProgramInfo(v8_profile.Snapshot.fromJson(json), |
| collapseAnonymousClosures: collapseAnonymousClosures); |
| } else { |
| return instruction_sizes.loadProgramInfo(json, |
| collapseAnonymousClosures: collapseAnonymousClosures); |
| } |
| } |
| |
| /// Compare two size profiles and return result of the comparison as a treemap. |
| Map<String, dynamic> buildComparisonTreemap(Object oldJson, Object newJson, |
| {TreemapFormat format = TreemapFormat.collapsed, |
| bool collapseAnonymousClosures = false}) { |
| final oldSizes = loadProgramInfoFromJson(oldJson, |
| collapseAnonymousClosures: collapseAnonymousClosures); |
| final newSizes = loadProgramInfoFromJson(newJson, |
| collapseAnonymousClosures: collapseAnonymousClosures); |
| |
| return compareProgramInfo(oldSizes, newSizes, format: format); |
| } |
| |
| Map<String, dynamic> compareProgramInfo( |
| ProgramInfo oldSizes, ProgramInfo newSizes, |
| {TreemapFormat format = TreemapFormat.collapsed}) { |
| final diff = computeDiff(oldSizes, newSizes); |
| return treemapFromInfo(diff, format: format); |
| } |
| |
| String formatPercent(int value, int total, {bool withSign = false}) { |
| final p = value / total * 100.0; |
| final sign = (withSign && value > 0) ? '+' : ''; |
| return '${sign}${p.toStringAsFixed(2)}%'; |
| } |
| |
| void printHistogram(ProgramInfo info, Histogram histogram, |
| {Iterable<String> prefix = const [], |
| Iterable<String> suffix = const [], |
| String sizeHeader = 'Size (Bytes)', |
| int maxWidth = 0}) { |
| final totalSize = info.totalSize; |
| final wasFiltered = totalSize != histogram.totalSize; |
| final table = AsciiTable(header: [ |
| for (var col in histogram.bucketInfo.nameComponents) Text.left(col), |
| Text.right(sizeHeader), |
| Text.right('Percent'), |
| if (wasFiltered) Text.right('Of total'), |
| ], maxWidth: maxWidth); |
| |
| final visibleRows = [prefix, suffix].expand((l) => l).toList(); |
| final visibleSize = |
| visibleRows.fold(0, (sum, key) => sum + histogram.buckets[key]); |
| final numRestRows = histogram.length - (suffix.length + prefix.length); |
| final hiddenRows = Set<String>.from(histogram.bySize) |
| .difference(Set<String>.from(visibleRows)); |
| final interestingHiddenRows = |
| hiddenRows.any((k) => histogram.buckets[k] != 0); |
| |
| if (prefix.isNotEmpty) { |
| for (var key in prefix) { |
| final size = histogram.buckets[key]; |
| table.addRow([ |
| ...histogram.bucketInfo.namesFromBucket(key), |
| size.toString(), |
| formatPercent(size, histogram.totalSize), |
| if (wasFiltered) formatPercent(size, totalSize), |
| ]); |
| } |
| table.addSeparator(interestingHiddenRows ? Separator.Wave : Separator.Line); |
| } |
| |
| if (interestingHiddenRows) { |
| final totalRestBytes = histogram.totalSize - visibleSize; |
| table.addTextSeparator( |
| '$numRestRows more rows accounting for ${totalRestBytes}' |
| ' (${formatPercent(totalRestBytes, totalSize)} of total) bytes'); |
| final avg = (totalRestBytes / numRestRows).round(); |
| table.addTextSeparator( |
| 'on average that is ${avg} (${formatPercent(avg, histogram.totalSize)})' |
| ' bytes per row'); |
| table.addSeparator(suffix.isNotEmpty ? Separator.Wave : Separator.Line); |
| } |
| |
| if (suffix.isNotEmpty) { |
| for (var key in suffix) { |
| table.addRow([ |
| ...histogram.bucketInfo.namesFromBucket(key), |
| histogram.buckets[key].toString(), |
| formatPercent(histogram.buckets[key], histogram.totalSize), |
| ]); |
| } |
| table.addSeparator(Separator.Line); |
| } |
| |
| table.render(); |
| |
| if (wasFiltered || visibleSize != histogram.totalSize) { |
| print('In visible rows: ${visibleSize}' |
| ' (${formatPercent(visibleSize, totalSize)} of total)'); |
| } |
| print('Total: ${totalSize} bytes'); |
| } |
| |
| List<String> partsForPath(String path) { |
| final parts = path.split('/'); |
| if (parts.first.startsWith('package:')) { |
| // Convert dot separated package name into a path from which this package originated. |
| parts.replaceRange(0, 1, parts.first.split('.')); |
| } |
| return parts; |
| } |