import 'dart:developer' as developer; import 'dart:io' as io; import 'dart:typed_data'; import 'package:analyzer/file_system/overlay_file_system.dart'; import 'package:analyzer/file_system/physical_file_system.dart'; import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart'; import 'package:heap_snapshot/analysis.dart'; import 'package:heap_snapshot/format.dart'; import 'package:vm_service/vm_service.dart'; Future main() async { timer.start(); var resourceProvider = OverlayResourceProvider( PhysicalResourceProvider.INSTANCE, ); var analyzerPath = '/Users/scheglov/Source/Dart/sdk.git/sdk/pkg/analyzer'; var filePath = '$analyzerPath/lib/src/dart/element/element.dart'; var collection = AnalysisContextCollectionImpl( resourceProvider: resourceProvider, includedPaths: [analyzerPath], ); var analysisDriver = collection.contextFor(filePath).driver; analysisDriver.priorityFiles = [filePath]; timer.reset(); await analysisDriver.getResolvedUnit(filePath); print('[+${timer.elapsedMilliseconds} ms] Get resolved unit'); var bytes = _getHeapSnapshot(); timer.reset(); var graph = HeapSnapshotGraph.fromChunks([ bytes.buffer.asByteData(bytes.offsetInBytes, bytes.length), ]); print('[+${timer.elapsedMilliseconds} ms] Create HeapSnapshotGraph'); var analysis = Analysis(graph); // Computing reachable objects takes some time. timer.reset(); analysis.reachableObjects; print('[+${timer.elapsedMilliseconds} ms] Compute reachable objects'); print(''); { var measure = analysis.measureObjects(analysis.reachableObjects); print('reachableObjects'); print(' count: ${measure.count}'); print(' size: ${formatBytes(measure.size)}'); } // It is interesting to see all reachable objects. { print('Reachable objects'); var objects = analysis.reachableObjects; analysis.printObjectStats(objects, maxLines: 100); } { print('\n\n'); print('Tokens'); var classSet = analysis.classByPredicate((e) { return e.name.endsWith('Token') || e.name.endsWith('TokenImpl'); }); var objects = analysis.filterByClassId(analysis.reachableObjects, classSet); analysis.printObjectStats(objects, maxLines: 100); print('\n\n'); print('Tokens retainers'); analysis.printRetainers(objects, maxEntries: 10); } } final Stopwatch timer = Stopwatch(); Future pumpEventQueue([int times = 5000]) { if (times == 0) return Future.value(); return Future.delayed(Duration.zero, () => pumpEventQueue(times - 1)); } Uint8List _getHeapSnapshot() { timer.reset(); var tmpDir = io.Directory.systemTemp.createTempSync('analyzer_heap'); try { var snapshotFile = io.File('${tmpDir.path}/0.heap_snapshot'); developer.NativeRuntime.writeHeapSnapshotToFile(snapshotFile.path); print('[+${timer.elapsedMilliseconds} ms] Write heap snapshot'); timer.reset(); var bytes = snapshotFile.readAsBytesSync(); print( '[+${timer.elapsedMilliseconds} ms] ' 'Read heap snapshot, ${bytes.length ~/ (1024 * 1024)} MB', ); return bytes; } finally { tmpDir.deleteSync(recursive: true); } } class _ObjectSetMeasure { final int count; final int size; _ObjectSetMeasure({required this.count, required this.size}); } extension on Analysis { IntSet classByPredicate(bool Function(HeapSnapshotClass) predicate) { var allClasses = graph.classes; var classSet = SpecializedIntSet(allClasses.length); for (var class_ in allClasses) { if (predicate(class_)) { classSet.add(class_.classId); } } return classSet; } // ignore: unused_element IntSet filterByClass( IntSet objectIds, { required Uri libraryUri, required String name, }) { var cid = graph.classes.singleWhere((class_) { return class_.libraryUri == libraryUri && class_.name == name; }).classId; return filter(objectIds, (object) => object.classId == cid); } _ObjectSetMeasure measureObjects(IntSet objectIds) { var stats = generateObjectStats(objectIds); var totalSize = 0; var totalCount = 0; for (var class_ in stats.classes) { totalCount += stats.counts[class_.classId]; totalSize += stats.sizes[class_.classId]; } return _ObjectSetMeasure(count: totalCount, size: totalSize); } void printObjectStats(IntSet objectIds, {int maxLines = 20}) { var stats = generateObjectStats(objectIds); print(formatHeapStats(stats, maxLines: maxLines)); print(''); } void printRetainers(IntSet objectIds, {int maxEntries = 3}) { var paths = retainingPathsOf(objectIds, 20); for (int i = 0; i < paths.length; ++i) { if (i >= maxEntries) break; var path = paths[i]; print('There are ${path.count} retaining paths of'); print(formatRetainingPath(graph, paths[i])); print(''); } } }