blob: 33e1d848a6c785e4931c1125b6d279f7642a78e2 [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 'dart:io';
import 'dart:convert';
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;
Future<Object> loadJson(File input) async {
return await input
.openRead()
.transform(utf8.decoder)
.transform(json.decoder)
.first;
}
Future<ProgramInfo> loadProgramInfo(File input,
{bool collapseAnonymousClosures = false}) async {
final json = await loadJson(input);
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.
Future<Map<String, dynamic>> buildComparisonTreemap(File oldJson, File newJson,
{TreemapFormat format = TreemapFormat.collapsed,
bool collapseAnonymousClosures = false}) async {
final oldSizes = await loadProgramInfo(oldJson,
collapseAnonymousClosures: collapseAnonymousClosures);
final newSizes = await loadProgramInfo(newJson,
collapseAnonymousClosures: collapseAnonymousClosures);
final diff = computeDiff(oldSizes, newSizes);
return treemapFromInfo(diff, format: format);
}
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);
String formatPercent(int value, int total) {
final p = value / total * 100.0;
return p.toStringAsFixed(2) + "%";
}
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(
prefix.length < histogram.length ? Separator.Wave : Separator.Line);
}
final visibleSize = [prefix, suffix]
.expand((l) => l)
.fold(0, (sum, key) => sum + histogram.buckets[key]);
final numRestRows = histogram.length - (suffix.length + prefix.length);
if (numRestRows > 0) {
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');
}