Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialization for diff and profile tabs. #7699

Merged
merged 40 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f167213
Squashed commit of the following:
polina-c May 5, 2024
45ccb07
-
polina-c May 5, 2024
498ad5d
Merge branch 'master' of github.com:flutter/devtools into offline1
polina-c May 6, 2024
33a0e6b
Update allocation_profile_table_view_test.dart
polina-c May 6, 2024
86b76e5
-
polina-c May 6, 2024
79a8e95
-
polina-c May 7, 2024
7fe77d5
Update memory_controller_test.dart
polina-c May 7, 2024
67d00ac
-
polina-c May 7, 2024
a687eee
Merge branch 'master' of github.com:flutter/devtools into offline1
polina-c May 7, 2024
2012225
-
polina-c May 8, 2024
ef553c7
Update chart_connection.dart
polina-c May 8, 2024
3d6eca0
-
polina-c May 8, 2024
951a301
-
polina-c May 8, 2024
f741271
-
polina-c May 9, 2024
d75a0ba
Update diff_pane_controller.dart
polina-c May 9, 2024
fde4f90
Update serialization.dart
polina-c May 9, 2024
3d1ba86
-
polina-c May 9, 2024
367e1a3
-
polina-c May 9, 2024
0f066d7
-
polina-c May 9, 2024
dacfbf2
-
polina-c May 9, 2024
ed85d23
Update chart_data_test.dart
polina-c May 9, 2024
dddd79a
-
polina-c May 10, 2024
23147a4
-
polina-c May 10, 2024
eba6ed7
-
polina-c May 14, 2024
50d45ff
Merge branch 'master' of github.com:flutter/devtools into offline1
polina-c May 17, 2024
bf1816b
-
polina-c May 17, 2024
74d5660
Update CHANGELOG.md
polina-c May 18, 2024
1608257
Merge branch 'master' of github.com:flutter/devtools into offline1
polina-c May 18, 2024
ef243b7
-
polina-c May 18, 2024
53beef4
-
polina-c May 18, 2024
ea44be6
Merge branch 'master' of github.com:flutter/devtools into offline1
polina-c May 20, 2024
3d9b81d
-
polina-c May 20, 2024
01b2639
-
polina-c May 20, 2024
27fbec4
-
polina-c May 20, 2024
7b55f4c
-
polina-c May 20, 2024
3ef1fe4
-
polina-c May 20, 2024
46416be
-
polina-c May 20, 2024
480ed24
Update heap_sample.dart
polina-c May 20, 2024
fca77e1
Update heap_sample_test.dart
polina-c May 20, 2024
f166ab6
-
polina-c May 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:devtools_shared/devtools_shared.dart';
import 'package:meta/meta.dart';

import '../../panes/chart/controller/chart_data.dart';
import '../../panes/diff/controller/diff_pane_controller.dart';
import '../../panes/profile/profile_pane_controller.dart';
import '../../shared/heap/class_filter.dart';

class _Json {
static const selectedTab = 'selectedTab';
static const classFilter = 'classFilter';
static const diffData = 'diffData';
static const profileData = 'profileData';
static const chartData = 'chartData';
@visibleForTesting
enum Json {
selectedTab,
classFilter,
diffData,
profileData,
chartData;
}

class OfflineMemoryData {
Expand All @@ -25,31 +29,38 @@ class OfflineMemoryData {
});

factory OfflineMemoryData.fromJson(Map<String, dynamic> json) {
Map<String, dynamic> item(String key) =>
json[key] as Map<String, dynamic>? ?? {};
return OfflineMemoryData(
DiffPaneController.fromJson(item(_Json.diffData)),
ProfilePaneController.fromJson(item(_Json.profileData)),
ChartData.fromJson(item(_Json.chartData)),
ClassFilter.fromJson(item(_Json.classFilter)),
selectedTab: json[_Json.selectedTab] as int? ?? 0,
deserialize<DiffPaneController>(
json[Json.diffData.name],
DiffPaneController.fromJson,
),
deserialize<ProfilePaneController>(
json[Json.profileData.name],
ProfilePaneController.fromJson,
),
deserialize<ChartData>(json[Json.chartData.name], ChartData.fromJson),
deserialize<ClassFilter>(
json[Json.classFilter.name],
ClassFilter.fromJson,
),
selectedTab: json[Json.selectedTab.name] as int? ?? 0,
);
}

Map<String, dynamic> toJson() {
return {
Json.selectedTab.name: selectedTab,
Json.diffData.name: diff,
Json.profileData.name: profile,
Json.chartData.name: chart,
Json.classFilter.name: filter,
};
}

final int selectedTab;
final ClassFilter filter; // filter is shared between tabs, so it's here

final DiffPaneController diff;
final ProfilePaneController profile;
final ChartData chart;

Map<String, dynamic> toJson() {
return {
_Json.selectedTab: selectedTab,
_Json.diffData: diff.toJson(),
_Json.profileData: profile.toJson(),
_Json.chartData: chart.toJson(),
_Json.classFilter: profile.classFilter.value.toJson(),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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

import '../../../../../shared/primitives/simple_items.dart';
import '../../../shared/primitives/memory_timeline.dart';
import '../data/primitives.dart';

class _Json {
static const isDeviceAndroid = 'isAndroid';
static const timeline = 'timeline';
static const interval = 'interval';
static const isLegendVisible = 'isLegendVisible';
@visibleForTesting
enum Json {
isDeviceAndroid,
timeline,
interval,
isLegendVisible;
}

/// Chart data, that should be saved when transferred to offline data mode.
Expand Down Expand Up @@ -40,22 +42,24 @@ class ChartData {
factory ChartData.fromJson(Map<String, dynamic> json) {
final result = ChartData(
mode: ControllerCreationMode.offlineData,
isDeviceAndroid: json[_Json.isDeviceAndroid] as bool? ?? false,
timeline:
MemoryTimeline.fromJson(json[_Json.timeline] as Map<String, dynamic>),
interval: ChartInterval.byName(json[_Json.interval]) ??
isDeviceAndroid: json[Json.isDeviceAndroid.name] as bool? ?? false,
timeline: deserialize<MemoryTimeline>(
json[Json.timeline.name],
MemoryTimeline.fromJson,
),
interval: ChartInterval.byName(json[Json.interval.name]) ??
ChartInterval.theDefault,
isLegendVisible: json[_Json.isLegendVisible] as bool?,
isLegendVisible: json[Json.isLegendVisible.name] as bool?,
);
return result;
}

Map<String, dynamic> toJson() {
return {
_Json.isDeviceAndroid: isDeviceAndroid ?? false,
_Json.timeline: timeline.toJson(),
_Json.interval: displayInterval.name,
_Json.isLegendVisible: isLegendVisible.value,
Json.isDeviceAndroid.name: isDeviceAndroid ?? false,
Json.timeline.name: timeline,
Json.interval.name: displayInterval.name,
Json.isLegendVisible.name: isLegendVisible.value,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ class EventChartController extends ChartController {
final data = chart_trace.DataAggregate(
sample.timestamp,
_Sizes.extensions,
(events.extensionEvents?.theEvents ?? []).length,
(events.extensionEvents?.events ?? []).length,
);
addDataToTrace(_EventsTraceName.extensionEvents.index, data);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ class ChartsValues {

if (eventInfo.hasExtensionEvents) {
final events = <Map<String, Object>>[];
for (ExtensionEvent event in eventInfo.extensionEvents?.theEvents ?? []) {
for (ExtensionEvent event in eventInfo.extensionEvents?.events ?? []) {
if (event.customEventName != null) {
events.add(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'dart:math';

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

import '../../../../../shared/analytics/analytics.dart' as ga;
Expand All @@ -26,19 +27,66 @@ import '../data/csv.dart';
import '../data/heap_diff_data.dart';
import '../data/heap_diff_store.dart';
import 'class_data.dart';
import 'item_controller.dart';
import 'snapshot_item.dart';

@visibleForTesting
enum Json {
snapshots,
diffWith;
}

class DiffPaneController extends DisposableController {
DiffPaneController({required this.loader});
DiffPaneController({
required this.loader,
List<SnapshotDataItem>? snapshots,
}) {
if (snapshots != null) {
core._snapshots.addAll(snapshots);
}
derived._updateValues();
}

factory DiffPaneController.fromJson(Map<String, dynamic> json) {
// TODO(polina-c): implement, https://github.com/flutter/devtools/issues/6972
return DiffPaneController(loader: null);
final snapshots = (json[Json.snapshots.name] as List)
.map((e) => deserialize<SnapshotDataItem>(e, SnapshotDataItem.fromJson))
.toList();

final diffWith =
(json[Json.diffWith.name] as List).map((e) => e as int?).toList();

assert(snapshots.length == diffWith.length);

for (var i = 0; i < snapshots.length; i++) {
kenzieschmoll marked this conversation as resolved.
Show resolved Hide resolved
polina-c marked this conversation as resolved.
Show resolved Hide resolved
final diffIndex = diffWith[i];
if (diffIndex != null) {
snapshots[i].diffWith.value = snapshots[diffIndex];
}
}

return DiffPaneController(
loader: null,
snapshots: snapshots,
);
}

Map<String, dynamic> toJson() {
// TODO(polina-c): implement, https://github.com/flutter/devtools/issues/6972
return {};
final snapshots = core.snapshots.value
.whereType<SnapshotDataItem>()
.where((s) => s.heap != null)
.toList();

final snapshotToIndex =
snapshots.asMap().map((index, item) => MapEntry(item, index));

final diffWithIndices = snapshots.map((item) {
final diffWith = item.diffWith.value;
return diffWith == null ? null : snapshotToIndex[diffWith];
}).toList();

return {
Json.snapshots.name: snapshots,
Json.diffWith.name: diffWithIndices,
};
}

final HeapGraphLoader? loader;
Expand Down Expand Up @@ -315,7 +363,7 @@ class DerivedData extends DisposableController with AutoDisposeControllerMixin {
var diffHidden = true;
var details = 'no data';
final item = selectedItem.value;
if (item is SnapshotDataItem && item.hasData) {
if (item is SnapshotDataItem && item.isProcessed) {
diffHidden = item.diffWith.value == null;
singleHidden = !diffHidden;
details = diffHidden ? 'single' : 'diff';
Expand All @@ -330,8 +378,14 @@ class DerivedData extends DisposableController with AutoDisposeControllerMixin {
assert(classesTableDiff.selection.value == null, details);
}

assert((singleClassesToShow.value == null) == singleHidden, details);
assert((diffClassesToShow.value == null) == diffHidden, details);
assert(
(singleClassesToShow.value == null) == singleHidden,
'$details, ${singleClassesToShow.value}, $singleHidden',
);
assert(
(diffClassesToShow.value == null) == diffHidden,
'$details, ${singleClassesToShow.value}, $singleHidden',
);

return true;
}());
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 All @@ -11,34 +13,58 @@ import '../../../../../shared/memory/heap_graph_loader.dart';
abstract class SnapshotItem extends DisposableController {
/// Number to show with auto-generated names that may be non unique, like isolate name.
int? get displayNumber;

ValueListenable<bool> get isProcessing => _isProcessing;
final _isProcessing = ValueNotifier<bool>(false);

/// If true, the item contains data, that can be compared and analyzed.
bool get hasData;

@override
void dispose() {
_isProcessing.dispose();
super.dispose();
}
}

class SnapshotDocItem extends SnapshotItem {
@override
int? get displayNumber => null;
}

@override
bool get hasData => false;
@visibleForTesting
enum Json {
defaultName,
displayNumber,
chunks,
created,
nameOverride;
}

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) {
final result = SnapshotDataItem(
displayNumber: json[Json.displayNumber.name] as int?,
defaultName: json[Json.defaultName.name] as String? ?? 'no name',
nameOverride: json[Json.nameOverride.name] as String?,
);

final chunks = json[Json.chunks.name] as List<ByteData>?;
if (chunks == null) return result;

final loader = HeapGraphLoaderFromChunks(
chunks: chunks,
created: json[Json.created.name] as DateTime? ?? DateTime.now(),
);

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

return result;
}

Map<String, dynamic> toJson() {
return {
Json.defaultName.name: defaultName,
Json.displayNumber.name: displayNumber,
Json.nameOverride.name: nameOverride,
Json.chunks.name: _heap?.graph.toChunks(),
Json.created.name: _heap?.created,
};
}

HeapData? get heap => _heap;
Expand All @@ -50,14 +76,12 @@ class SnapshotDataItem extends SnapshotItem implements RenamableItem {
@override
final int? displayNumber;

@override
bool get hasData => _heap != null;

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);
await _heap!.calculate;
_processed.complete();
}

@override
Expand All @@ -71,6 +95,10 @@ class SnapshotDataItem extends SnapshotItem implements RenamableItem {
'$defaultName${displayNumber == null ? '' : '-$displayNumber'}';

int? get totalSize => _heap?.footprint?.reachable;

Future<void> get process => _processed.future;
final _processed = Completer<void>();
bool get isProcessed => _processed.isCompleted;
}

abstract class RenamableItem {
Expand Down