| // 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'); |
| } |