blob: 241c7ae8cf92fd6191bdd821f60aafbbfe5ef58e [file] [log] [blame]
// Copyright (c) 2022, 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.
// ignore_for_file: prefer_interpolation_to_compose_strings
// ignore_for_file: unnecessary_string_interpolations
import 'dart:typed_data';
import 'package:vm_service/vm_service.dart';
import 'analysis.dart';
String format(int a) => a.toString().padLeft(6, ' ');
String formatBytes(int a) => (a ~/ 1024).toString().padLeft(6, ' ') + ' kb';
String truncateString(String s) {
int index;
index = s.indexOf('\n');
if (index >= 0) s = s.substring(index);
index = s.indexOf('\r');
if (index >= 0) s = s.substring(index);
if (s.length > 30) s = s.substring(30);
return s;
}
String formatHeapStats(HeapStats stats, {int? maxLines, int? sizeCutoff}) {
assert(sizeCutoff == null || sizeCutoff >= 0);
assert(maxLines == null || maxLines >= 0);
final table = Table();
table.addRow(['size', 'count', 'class']);
table.addRow(['--------', '--------', '--------']);
int totalSize = 0;
int totalCount = 0;
for (int i = 0; i < stats.classes.length; ++i) {
final c = stats.classes[i];
final count = stats.counts[c.classId];
final size = stats.sizes[c.classId];
totalSize += size;
totalCount += count;
if (sizeCutoff == null || size >= sizeCutoff) {
if (maxLines == null || i < maxLines) {
table.addRow(
[formatBytes(size), format(count), '${c.name} ${c.libraryUri}']);
}
}
}
if (table.rows > 3) {
table.addRow(['--------', '--------']);
table.addRow([formatBytes(totalSize), format(totalCount)]);
}
return table.asString;
}
String formatDataStats(HeapDataStats stats, {int? maxLines, int? sizeCutoff}) {
assert(sizeCutoff == null || sizeCutoff >= 0);
assert(maxLines == null || maxLines >= 0);
final table = Table();
table.addRow(['size', 'unique-size', 'count', 'class', 'data']);
table.addRow(['--------', '--------', '--------', '--------', '--------']);
int totalSize = 0;
int totalUniqueSize = 0;
int totalCount = 0;
final List<HeapData> datas = stats.datas;
for (int i = 0; i < datas.length; ++i) {
final data = datas[i];
totalSize += data.size;
totalUniqueSize += data.totalSize;
totalCount += data.count;
if (sizeCutoff == null || data.totalSize >= sizeCutoff) {
if (maxLines == null || i < maxLines) {
table.addRow([
formatBytes(data.totalSize),
formatBytes(data.size),
format(data.count),
data.klass,
data.valueAsString,
]);
}
}
}
if (table.rows > 3) {
table.addRow(['--------', '--------', '--------']);
table.addRow([
formatBytes(totalUniqueSize),
formatBytes(totalSize),
format(totalCount)
]);
}
return table.asString;
}
String formatRetainingPath(HeapSnapshotGraph graph, DedupedUint32List rpath) {
final path = _stringifyRetainingPath(graph, rpath);
final bool wasTruncated = rpath.path.last != /*root*/ 1;
final sb = StringBuffer();
for (int i = 0; i < path.length; ++i) {
final indent = i >= 2 ? (i - 1) : 0;
sb.writeln(' ' * 4 * indent + (i == 0 ? '' : '⮑ ') + '${path[i]}');
}
if (wasTruncated) {
sb.writeln(' ' * 4 * (path.length - 1) + '⮑ …');
}
return sb.toString();
}
String formatDominatorPath(HeapSnapshotGraph graph, DedupedUint32List dpath) {
final path = _stringifyDominatorPath(graph, dpath);
final bool wasTruncated = dpath.path.last != /*root*/ 1;
final sb = StringBuffer();
for (int i = 0; i < path.length; ++i) {
final indent = i >= 2 ? (i - 1) : 0;
sb.writeln(' ' * 4 * indent + (i == 0 ? '' : '⮑ ') + '${path[i]}');
}
if (wasTruncated) {
sb.writeln(' ' * 4 * (path.length - 1) + '⮑ …');
}
return sb.toString();
}
List<String> _stringifyRetainingPath(
HeapSnapshotGraph graph, DedupedUint32List rpath) {
final path = rpath.path;
final spath = <String>[];
for (int i = 0; i < path.length; i += 2) {
final klass = graph.classes[path[i]];
String? fieldName;
String prefix = '';
if (i > 0) {
final int value = path[i - 1];
final hasUniqueOwner = (value & (1 << 0)) == 1;
final fieldIndex = value >> 1;
if (fieldIndex != DedupedUint32List.noFieldIndex) {
final field = klass.fields[fieldIndex];
assert(field.index == fieldIndex);
fieldName = field.name;
}
prefix = (hasUniqueOwner ? '・' : '﹢');
}
spath.add(prefix +
'${klass.name}' +
(fieldName != null ? '.$fieldName' : '') +
' (${klass.libraryUri})');
}
return spath;
}
List<String> _stringifyDominatorPath(
HeapSnapshotGraph graph, DedupedUint32List rpath) {
final path = rpath.path;
final spath = <String>[];
for (int i = 0; i < path.length; i++) {
final klass = graph.classes[path[i]];
spath.add('${klass.name} (${klass.libraryUri})');
}
return spath;
}
class Table {
final List<List<String>> _rows = [];
int _maxColumn = -1;
int get rows => _rows.length;
void addRow(List<String> row) {
_maxColumn = row.length > _maxColumn ? row.length : _maxColumn;
_rows.add(row);
}
String get asString {
if (_rows.isEmpty) return '';
final colSizes = Uint32List(_maxColumn);
for (final row in _rows) {
for (int i = 0; i < row.length; ++i) {
final value = row[i];
final c = colSizes[i];
if (value.length > c) colSizes[i] = value.length;
}
}
final sb = StringBuffer();
for (final row in _rows) {
for (int i = 0; i < row.length; ++i) {
row[i] = row[i].padRight(colSizes[i], ' ');
}
sb.writeln(row.join(' '));
}
return sb.toString().trimRight();
}
}
String indent(String left, String text) {
return left + text.replaceAll('\n', '\n$left');
}