blob: abf3eb32ca17a87e077a79458a70d60c8eff200e [file] [log] [blame]
// 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;
}