- Add VariableSpec class with magnitude field validation - Add Formula class supporting multiple input/output variables - Support d4rt_code as string or object with code field - Add comprehensive tests for parsing and serialization - Fix broken test import in pruebas_d4rt_test.dart Follows README.md format requirements exactly
321 lines
9.2 KiB
Dart
321 lines
9.2 KiB
Dart
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:analyzer/src/dart/analysis/byte_store.dart';
|
|
import 'package:analyzer/src/dart/analysis/driver_event.dart';
|
|
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
|
|
import 'package:analyzer/src/dart/analysis/results.dart';
|
|
import 'package:heap_snapshot/analysis.dart';
|
|
import 'package:heap_snapshot/format.dart';
|
|
import 'package:linter/src/rules.dart';
|
|
import 'package:vm_service/vm_service.dart';
|
|
|
|
void main() async {
|
|
var resourceProvider = OverlayResourceProvider(
|
|
PhysicalResourceProvider.INSTANCE,
|
|
);
|
|
|
|
registerLintRules();
|
|
|
|
var byteStore = NullByteStore();
|
|
|
|
var packageRootPath = '/Users/scheglov/dart/admin-portal';
|
|
var libPath = '$packageRootPath/lib';
|
|
|
|
// var builder = PackageConfigFileBuilder()
|
|
// ..add(
|
|
// name: 'test',
|
|
// rootPath: packageRootPath,
|
|
// );
|
|
//
|
|
// resourceProvider.setOverlay(
|
|
// '$packageRootPath/.dart_tool/package_config.json',
|
|
// content: builder.toContent(
|
|
// toUriStr: (path) {
|
|
// var pathContext = resourceProvider.pathContext;
|
|
// return pathContext.toUri(path).toString();
|
|
// },
|
|
// ),
|
|
// modificationStamp: 0,
|
|
// );
|
|
|
|
var modelPath = '$libPath/data/models/group_model.dart';
|
|
var modelCode = resourceProvider.getFile(modelPath).readAsStringSync();
|
|
|
|
var collection = AnalysisContextCollectionImpl(
|
|
resourceProvider: resourceProvider,
|
|
// includedPaths: [libPath],
|
|
includedPaths: [
|
|
'/Users/scheglov/dart/admin-portal/lib/data/models/group_model.dart',
|
|
'/Users/scheglov/dart/admin-portal/lib/utils/formatting.dart',
|
|
'/Users/scheglov/dart/admin-portal/lib/redux/company/company_selectors.dart',
|
|
],
|
|
byteStore: byteStore,
|
|
performanceLog: PerformanceLog(io.stdout),
|
|
drainStreams: false,
|
|
);
|
|
|
|
var analysisContext = collection.contextFor(modelPath);
|
|
for (var path in analysisContext.contextRoot.analyzedFiles()) {
|
|
if (path.endsWith('.dart')) {
|
|
analysisContext.driver.addFile(path);
|
|
}
|
|
}
|
|
|
|
analysisContext.driver.scheduler.events.listen((event) {
|
|
switch (event) {
|
|
case AnalyzeFile analyzeFile:
|
|
print('[events][analyzeFile][file: ${analyzeFile.file}]');
|
|
case ResolvedUnitResultImpl unitResult:
|
|
print('[events][resolvedUnit][file: ${unitResult.file}]');
|
|
print(' [events][errors: ${unitResult.errors}]');
|
|
default:
|
|
print('[events][event: $event]');
|
|
}
|
|
});
|
|
|
|
await collection.scheduler.waitForIdle();
|
|
await pumpEventQueue();
|
|
print('\n' * 2);
|
|
print('[S] Now idle');
|
|
print('-' * 64);
|
|
|
|
{
|
|
var heapBytes = _getHeapSnapshot();
|
|
_analyzeSnapshot(heapBytes);
|
|
}
|
|
print('\n' * 2);
|
|
print('[S] Printed heap analysis');
|
|
print('-' * 64);
|
|
|
|
resourceProvider.setOverlay(
|
|
modelPath,
|
|
content: modelCode.replaceAll(
|
|
'bool get hasCurrency =>',
|
|
'bool get hasCurrency2 =>',
|
|
),
|
|
modificationStamp: 1,
|
|
);
|
|
analysisContext.changeFile(modelPath);
|
|
print('[S] hasCurrency -> hasCurrency2');
|
|
print('\n' * 2);
|
|
await collection.scheduler.waitForIdle();
|
|
await pumpEventQueue();
|
|
print('\n' * 2);
|
|
print('[S] Now idle');
|
|
print('-' * 64);
|
|
|
|
resourceProvider.setOverlay(
|
|
modelPath,
|
|
content: modelCode.replaceAll(
|
|
'bool get hasCurrency2 =>',
|
|
'bool get hasCurrency =>',
|
|
),
|
|
modificationStamp: 2,
|
|
);
|
|
analysisContext.changeFile(modelPath);
|
|
print('[S] hasCurrency2 -> hasCurrency');
|
|
print('\n' * 2);
|
|
await collection.scheduler.waitForIdle();
|
|
await pumpEventQueue();
|
|
print('\n' * 2);
|
|
print('[S] Now idle');
|
|
print('-' * 64);
|
|
|
|
resourceProvider.setOverlay(
|
|
modelPath,
|
|
content: modelCode.replaceAll(
|
|
'bool get hasCurrency =>',
|
|
'bool get hasCurrency2 =>',
|
|
),
|
|
modificationStamp: 3,
|
|
);
|
|
analysisContext.changeFile(modelPath);
|
|
print('[S] hasCurrency -> hasCurrency2');
|
|
print('\n' * 2);
|
|
await collection.scheduler.waitForIdle();
|
|
await pumpEventQueue();
|
|
print('\n' * 2);
|
|
print('[S] Now idle');
|
|
print('-' * 64);
|
|
|
|
print('[S] Disposing...');
|
|
await collection.dispose();
|
|
}
|
|
|
|
final Stopwatch timer = Stopwatch();
|
|
|
|
Future pumpEventQueue([int times = 5000]) {
|
|
if (times == 0) return Future.value();
|
|
return Future.delayed(Duration.zero, () => pumpEventQueue(times - 1));
|
|
}
|
|
|
|
void _analyzeSnapshot(Uint8List bytes) {
|
|
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);
|
|
// allResults.add(
|
|
// BenchmarkResultCompound(name: 'reachableObjects', children: [
|
|
// BenchmarkResultCount(
|
|
// name: 'count',
|
|
// value: measure.count,
|
|
// ),
|
|
// BenchmarkResultBytes(
|
|
// name: 'size',
|
|
// value: 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');
|
|
// print('Instances of: _GrowableList');
|
|
// final objectList = analysis.filter(analysis.reachableObjects, (object) {
|
|
// return object.klass.libraryUri == Uri.parse('dart:core') &&
|
|
// object.klass.name == '_GrowableList';
|
|
// // return analysis.variableLengthOf(object) == 0;
|
|
// });
|
|
//
|
|
// // final objectList = analysis.filterByClassPatterns(
|
|
// // analysis.reachableObjects,
|
|
// // ['_GrowableList'],
|
|
// // );
|
|
// final stats = analysis.generateObjectStats(objectList);
|
|
// print(formatHeapStats(stats, maxLines: 20));
|
|
// print('');
|
|
//
|
|
// const maxEntries = 10;
|
|
// final paths = analysis.retainingPathsOf(objectList, 10);
|
|
// for (int i = 0; i < paths.length; ++i) {
|
|
// if (maxEntries != -1 && i >= maxEntries) break;
|
|
// final path = paths[i];
|
|
// print('There are ${path.count} retaining paths of');
|
|
// print(formatRetainingPath(analysis.graph, paths[i]));
|
|
// print('');
|
|
// }
|
|
// }
|
|
|
|
// timer.reset();
|
|
//
|
|
// allResults.add(
|
|
// _doUniqueUriStr(analysis),
|
|
// );
|
|
//
|
|
// allResults.add(
|
|
// _doInterfaceType(analysis),
|
|
// );
|
|
//
|
|
// allResults.add(
|
|
// _doLinkedData(analysis),
|
|
// );
|
|
//
|
|
// print('[+${timer.elapsedMilliseconds} ms] Compute benchmark results');
|
|
// print('');
|
|
//
|
|
// return allResults;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
// }
|
|
|
|
// 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('');
|
|
}
|
|
|
|
// ignore: unused_element
|
|
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('');
|
|
}
|
|
}
|
|
}
|
|
|
|
// class _ObjectSetMeasure {
|
|
// final int count;
|
|
// final int size;
|
|
//
|
|
// _ObjectSetMeasure({required this.count, required this.size});
|
|
// }
|