Skip to content

Commit

Permalink
-
Browse files Browse the repository at this point in the history
  • Loading branch information
polina-c committed Apr 28, 2024
1 parent 5a30bc4 commit e21cb05
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ class DiffPaneController extends DisposableController {
}

Map<String, dynamic> toJson() {
final snapshots =
core.snapshots.value.whereType<SnapshotDataItem>().toList();
final snapshots = core.snapshots.value
.whereType<SnapshotDataItem>()
.where((s) => s.heap != null)
.toList();

final snapshotToIndex =
snapshots.asMap().map((index, item) => MapEntry(item, index));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'package:devtools_app_shared/utils.dart';
import 'package:flutter/foundation.dart';

Expand Down Expand Up @@ -36,25 +38,46 @@ class SnapshotDocItem extends SnapshotItem {
class _Json {
static const defaultName = 'defaultName';
static const displayNumber = 'displayNumber';
static const snapshot = 'snapshot';
static const chunks = 'chunks';
static const created = 'created';
static const nameOverride = 'nameOverride';
static const diffWith = 'diffWith';
}

class SnapshotDataItem extends SnapshotItem implements RenamableItem {
SnapshotDataItem({
this.displayNumber,
required this.defaultName,
}) {
_isProcessing.value = true;
}
this.displayNumber,
this.nameOverride,
});

factory SnapshotDataItem.fromJson(Map<String, dynamic> json) {
throw UnimplementedError();
final result = SnapshotDataItem(
displayNumber: json[_Json.displayNumber] as int?,
defaultName: json[_Json.defaultName] as String,
nameOverride: json[_Json.nameOverride] as String?,
);

final loader = HeapGraphLoaderFromChunks(
chunks: json[_Json.chunks] as List<ByteData>,
created: json[_Json.created] as DateTime,
);

unawaited(
result.loadHeap(loader),
); // Start the loading process, that will result in progress indicator in UI.

return result;
}

Map<String, dynamic> toJson() {
return {};
final heap = _heap!; // Not processed heaps are not serializable.
return {
_Json.defaultName: defaultName,
_Json.displayNumber: displayNumber,
_Json.nameOverride: nameOverride,
_Json.chunks: heap.graph.toChunks(),
_Json.created: heap.created,
};
}

HeapData? get heap => _heap;
Expand All @@ -72,8 +95,9 @@ class SnapshotDataItem extends SnapshotItem implements RenamableItem {
Future<void> loadHeap(HeapGraphLoader loader) async {
assert(_heap == null);
final (graph, created) = await loader.load();
_heap = await HeapData.calculate(graph, created);
_isProcessing.value = false;
_heap = HeapData(graph, created: created);
_isProcessing.value = true;
}

@override
Expand Down
61 changes: 23 additions & 38 deletions packages/devtools_app/lib/src/shared/memory/heap_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:vm_service/vm_service.dart';

Expand All @@ -12,25 +14,28 @@ import 'retainers.dart';
import 'simple_items.dart';

/// Raw and calculated data of the heap snapshot.
@immutable
class HeapData {
HeapData._(
this.graph,
this.classes,
this.footprint, {
HeapData(
this.graph, {
required this.created,
required this.retainedSizes,
});
@visibleForTesting bool startCalculation = true,
}) {
if (startCalculation) unawaited(_calculate());
}

final HeapSnapshotGraph graph;

final ClassDataList<SingleClassData>? classes;
final DateTime created;

final MemoryFootprint? footprint;
Future<void> get calculate => _calculated.future;
final _calculated = Completer<void>();
bool get isCalculated => _calculated.isCompleted;

final List<int>? retainedSizes;
ClassDataList<SingleClassData>? classes;

final DateTime created;
MemoryFootprint? footprint;

List<int>? retainedSizes;

/// Object index with the given identityHashCode.
///
Expand All @@ -49,25 +54,14 @@ class HeapData {
static final _uiReleaser = UiReleaser();

/// Calculate the heap data from the given [graph].
static Future<HeapData> calculate(
HeapSnapshotGraph graph,
DateTime created, {
Future<void> _calculate({
@visibleForTesting bool calculateRetainingPaths = true,
@visibleForTesting bool calculateRetainedSizes = true,
@visibleForTesting bool calculateClassData = true,
}) async {
if (!calculateClassData) {
return HeapData._(
graph,
null,
null,
created: created,
retainedSizes: null,
);
}
if (!calculateClassData) return;

List<int>? retainers;
List<int>? retainedSizes;

if (calculateRetainingPaths || calculateRetainedSizes) {
final weakClasses = _WeakClasses(graph);
Expand All @@ -85,13 +79,10 @@ class HeapData {
if (calculateRetainedSizes) retainedSizes = result.retainedSizes;
}

ClassDataList<SingleClassData>? classDataList;
MemoryFootprint? footprint;

// Complexity of this part is O(n)*O(p) where
// n is number of objects and p is length of retaining path.
if (calculateClassData) {
final classes = <HeapClassName, SingleClassData>{};
final nameToClass = <HeapClassName, SingleClassData>{};
int dartSize = 0;
int reachableSize = graph.objects[heapRootIndex].shallowSize;

Expand All @@ -117,7 +108,7 @@ class HeapData {

reachableSize += object.shallowSize;

classes
nameToClass
.putIfAbsent(
className,
() => SingleClassData(className: className),
Expand All @@ -131,22 +122,16 @@ class HeapData {
}

footprint = MemoryFootprint(dart: dartSize, reachable: reachableSize);
classDataList = ClassDataList<SingleClassData>(classes.values.toList());
classes = ClassDataList<SingleClassData>(nameToClass.values.toList());

// Check that retained size of root is the entire reachable heap.
assert(
retainedSizes == null ||
retainedSizes[heapRootIndex] == footprint.reachable,
retainedSizes![heapRootIndex] == footprint!.reachable,
);
}

return HeapData._(
graph,
classDataList,
footprint,
created: created,
retainedSizes: retainedSizes,
);
_calculated.complete();
}
}

Expand Down
15 changes: 15 additions & 0 deletions packages/devtools_app/lib/src/shared/memory/heap_graph_loader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:typed_data';

import 'package:file_selector/file_selector.dart';
import 'package:vm_service/vm_service.dart';

Expand Down Expand Up @@ -44,3 +46,16 @@ class HeapGraphLoaderFile implements HeapGraphLoader {
);
}
}

class HeapGraphLoaderFromChunks implements HeapGraphLoader {
HeapGraphLoaderFromChunks({required this.chunks, required this.created});

List<ByteData> chunks;

DateTime created;

@override
Future<(HeapSnapshotGraph, DateTime)> load() async {
return (HeapSnapshotGraph.fromChunks(chunks), created);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ void main() {
final before = ProcessInfo.currentRss;
for (var t in goldenHeapTests) {
snapshots[t.fileName] =
await HeapData.calculate(await t.loadHeap(), DateTime.now());
HeapData(await t.loadHeap(), created: DateTime.now());
await snapshots[t.fileName]!.calculate;
}

final after = ProcessInfo.currentRss;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import '../../../test_infra/scenes/memory/default.dart';

void main() {
test('Many retaining paths do not jank UI.', () async {
final heap = await HeapData.calculate(
final heap = HeapData(
await MemoryDefaultSceneHeaps.manyPaths(),
DateTime.now(),
created: DateTime.now(),
);
await heap.calculate;
final data = heap.classes!.list
.firstWhere((c) => c.className.className == 'TheData');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ void main() {
setUp(() async {
PathFromRoot.resetSingletons();

heap = await HeapData.calculate(await t.loadHeap(), DateTime.now());
heap = HeapData(await t.loadHeap(), created: DateTime.now());
await heap.calculate;
expect(PathFromRoot.debugUsage.stored, isPositive);
expect(
PathFromRoot.debugUsage.constructed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import '../../../test_infra/test_data/memory/heap/heap_graph_fakes.dart';
void main() {
for (var t in _sizeTests) {
test('has expected root and unreachable sizes, ${t.name}.', () async {
final heap = await HeapData.calculate(
final heap = HeapData(
t.heap,
DateTime.now(),
created: DateTime.now(),
);
await heap.calculate;

expect(
heap.retainedSizes![heapRootIndex],
Expand Down
6 changes: 5 additions & 1 deletion packages/devtools_app/test/memory/shared/heap/heap_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,11 @@ void main() {
test(
'$SingleClassData does not double-count self-referenced classes, ${t.name}.',
() async {
final heapData = await HeapData.calculate(t.heap, DateTime.now());
final heapData = HeapData(
t.heap,
created: DateTime.now(),
);
await heapData.calculate;

final classes = heapData.classes!;
final classData = classes.byName(_classA)!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ import 'package:vm_service/vm_service.dart';

import 'heap_graph_fakes.dart';

Future<HeapData> testHeapData([FakeHeapSnapshotGraph? graph]) async =>
await HeapData.calculate(
graph ?? FakeHeapSnapshotGraph(),
DateTime.now(),
);
Future<HeapData> testHeapData([FakeHeapSnapshotGraph? graph]) async {
final heapData = HeapData(
graph ?? FakeHeapSnapshotGraph(),
created: DateTime.now(),
);
await heapData.calculate;
return heapData;
}

SingleClassData testClassData(
HeapClassName className,
Expand Down

0 comments on commit e21cb05

Please sign in to comment.