Skip to content

Commit

Permalink
Initial structure for offline memory screen. (flutter#5965)
Browse files Browse the repository at this point in the history
  • Loading branch information
polina-c committed Jul 6, 2023
1 parent 2b972e6 commit 54111ea
Show file tree
Hide file tree
Showing 29 changed files with 266 additions and 272 deletions.
4 changes: 2 additions & 2 deletions packages/devtools_app/lib/devtools_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export 'src/screens/inspector/inspector_screen.dart';
export 'src/screens/inspector/inspector_tree_controller.dart';
export 'src/screens/logging/logging_controller.dart';
export 'src/screens/logging/logging_screen.dart';
export 'src/screens/memory/memory_controller.dart';
export 'src/screens/memory/memory_screen.dart';
export 'src/screens/memory/framework/connected/memory_controller.dart';
export 'src/screens/memory/framework/memory_screen.dart';
export 'src/screens/network/network_controller.dart';
export 'src/screens/network/network_model.dart';
export 'src/screens/network/network_screen.dart';
Expand Down
17 changes: 15 additions & 2 deletions packages/devtools_app/lib/src/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import 'screens/inspector/inspector_screen.dart';
import 'screens/inspector/inspector_tree_controller.dart';
import 'screens/logging/logging_controller.dart';
import 'screens/logging/logging_screen.dart';
import 'screens/memory/memory_controller.dart';
import 'screens/memory/memory_screen.dart';
import 'screens/memory/framework/connected/memory_controller.dart';
import 'screens/memory/framework/memory_screen.dart';
import 'screens/memory/framework/static/static_screen_body.dart';
import 'screens/network/network_controller.dart';
import 'screens/network/network_screen.dart';
import 'screens/performance/performance_controller.dart';
Expand Down Expand Up @@ -290,6 +291,18 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
),
);
},
if (FeatureFlags.memoryAnalysis)
memoryAnalysisScreenId: (_, __, args, ____) {
final embed = isEmbedded(args);
return DevToolsScaffold.withChild(
key: const Key('memoryanalysis'),
embed: embed,
child: MultiProvider(
providers: _providedControllers(),
child: const StaticMemoryBody(),
),
);
},
if (FeatureFlags.vsCodeSidebarTooling) ..._standaloneScreens,
};
}
Expand Down
47 changes: 47 additions & 0 deletions packages/devtools_app/lib/src/framework/landing_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import '../shared/analytics/analytics.dart' as ga;
import '../shared/analytics/constants.dart' as gac;
import '../shared/common_widgets.dart';
import '../shared/config_specific/import_export/import_export.dart';
import '../shared/feature_flags.dart';
import '../shared/file_import.dart';
import '../shared/globals.dart';
import '../shared/primitives/blocking_action_mixin.dart';
Expand Down Expand Up @@ -56,6 +57,10 @@ class _LandingScreenBodyState extends State<LandingScreenBody> {
ImportFileInstructions(sampleData: widget.sampleData),
const SizedBox(height: defaultSpacing),
const AppSizeToolingInstructions(),
if (FeatureFlags.memoryAnalysis) ...[
const SizedBox(height: defaultSpacing),
const MemoryAnalysisInstructions(),
],
],
),
);
Expand Down Expand Up @@ -352,6 +357,48 @@ class AppSizeToolingInstructions extends StatelessWidget {
}
}

@visibleForTesting
class MemoryAnalysisInstructions extends StatelessWidget {
const MemoryAnalysisInstructions({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return LandingScreenSection(
title: 'Memory Analysis',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Analyze and diff the saved memory snapshots',
style: textTheme.titleMedium,
),
const SizedBox(height: denseRowSpacing),
Text(
// TODO(polina-c): make package:leak_tracker a link.
// https://github.com/flutter/devtools/issues/5606
'Analyze heap snapshots that were previously saved from DevTools or package:leak_tracker.',
style: textTheme.bodySmall,
),
const SizedBox(height: defaultSpacing),
ElevatedButton(
child: const Text('Open memory analysis tool'),
onPressed: () => _onOpen(context),
),
],
),
);
}

void _onOpen(BuildContext context) {
ga.select(
gac.landingScreen,
gac.openMemoryAnalysisTool,
);
DevToolsRouterDelegate.of(context).navigate(memoryAnalysisScreenId);
}
}

class SampleDataDropDownButton extends StatefulWidget {
const SampleDataDropDownButton({
super.key,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This folder contains code not used by memory panes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';

import '../../../../shared/banner_messages.dart';
import '../../../../shared/primitives/auto_dispose.dart';
import '../../../../shared/primitives/simple_items.dart';
import '../../../../shared/theme.dart';
import '../../../../shared/utils.dart';
import '../../panes/chart/chart_pane.dart';
import '../../panes/chart/chart_pane_controller.dart';
import '../../panes/chart/memory_android_chart.dart';
import '../../panes/chart/memory_events_pane.dart';
import '../../panes/chart/memory_vm_chart.dart';
import '../../panes/control/control_pane.dart';
import 'memory_controller.dart';
import 'memory_tabs.dart';

class ConnectedMemoryBody extends StatefulWidget {
const ConnectedMemoryBody({super.key});

@override
State<ConnectedMemoryBody> createState() => _ConnectedMemoryBodyState();
}

class _ConnectedMemoryBodyState extends State<ConnectedMemoryBody>
with
AutoDisposeMixin,
SingleTickerProviderStateMixin,
ProvidedControllerMixin<MemoryController, ConnectedMemoryBody> {
MemoryController get memoryController => controller;

late MemoryChartPaneController _chartController;

final _focusNode = FocusNode(debugLabel: 'memory');

@override
void initState() {
super.initState();
autoDisposeFocusNode(_focusNode);
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
maybePushDebugModeMemoryMessage(context, ScreenMetaData.memory.id);
if (!initController()) return;

final vmChartController = VMChartController(controller);
_chartController = MemoryChartPaneController(
event: EventChartController(controller),
vm: vmChartController,
android: AndroidChartController(
controller,
sharedLabels: vmChartController.labelTimestamps,
),
);
}

@override
Widget build(BuildContext context) {
return Column(
key: MemoryChartPane.hoverKey,
children: [
MemoryControlPane(controller: controller),
const SizedBox(height: intermediateSpacing),
MemoryChartPane(
chartController: _chartController,
keyFocusNode: _focusNode,
),
Expanded(
child: MemoryTabView(memoryController),
),
],
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,20 @@ import 'dart:async';

import 'package:devtools_shared/devtools_shared.dart';
import 'package:flutter/foundation.dart';
import 'package:intl/intl.dart';
import 'package:leak_tracker/devtools_integration.dart';
import 'package:logging/logging.dart';
import 'package:vm_service/vm_service.dart';

import '../../service/service_manager.dart';
import '../../shared/analytics/analytics.dart' as ga;
import '../../shared/analytics/constants.dart' as gac;
import '../../shared/config_specific/file/file.dart';
import '../../shared/globals.dart';
import '../../shared/primitives/auto_dispose.dart';
import '../../shared/utils.dart';
import '../../../../service/service_manager.dart';
import '../../../../shared/globals.dart';
import '../../../../shared/primitives/auto_dispose.dart';
import '../../../../shared/utils.dart';
import '../../panes/chart/primitives.dart';
import '../../panes/diff/controller/diff_pane_controller.dart';
import '../../panes/profile/profile_pane_controller.dart';
import '../../panes/tracing/tracing_pane_controller.dart';
import '../../shared/heap/model.dart';
import '../../shared/primitives/memory_timeline.dart';
import 'memory_protocol.dart';
import 'panes/chart/primitives.dart';
import 'panes/diff/controller/diff_pane_controller.dart';
import 'panes/profile/profile_pane_controller.dart';
import 'panes/tracing/tracing_pane_controller.dart';
import 'shared/heap/model.dart';
import 'shared/primitives/memory_timeline.dart';

final _log = Logger('memory_controller');

// TODO(terry): Consider supporting more than one file since app was launched.
// Memory Log filename.
final String _memoryLogFilename =
'${MemoryController.logFilenamePrefix}${DateFormat("yyyyMMdd_HH_mm").format(DateTime.now())}';

class OfflineFileException implements Exception {
OfflineFileException(this.message) : super();

final String message;

@override
String toString() => message;
}

class MemoryFeatureControllers {
/// [diffPaneController] is passed for testability.
Expand Down Expand Up @@ -85,7 +64,6 @@ class MemoryController extends DisposableController
ProfilePaneController? profilePaneController,
}) {
memoryTimeline = MemoryTimeline();
memoryLog = MemoryLog(this);

controllers = MemoryFeatureControllers(
diffPaneController,
Expand All @@ -104,15 +82,11 @@ class MemoryController extends DisposableController
/// instead of the widget state.
int selectedFeatureTabIndex = 0;

static const logFilenamePrefix = 'memory_log_';

final _shouldShowLeaksTab = ValueNotifier<bool>(false);
ValueListenable<bool> get shouldShowLeaksTab => _shouldShowLeaksTab;

late MemoryTimeline memoryTimeline;

late MemoryLog memoryLog;

HeapSample? _selectedDartSample;

HeapSample? _selectedAndroidSample;
Expand Down Expand Up @@ -361,96 +335,3 @@ class MemoryController extends DisposableController
controllers.dispose();
}
}

/// Supports saving and loading memory samples.
class MemoryLog {
MemoryLog(this.controller);

/// Use in memory or local file system based on Flutter Web/Desktop.
static final _fs = FileIO();

MemoryController controller;

/// Persist the live memory data to a JSON file in the /tmp directory.
List<String> exportMemory() {
ga.select(gac.memory, gac.export);

final liveData = controller.memoryTimeline.liveData;

bool pseudoData = false;
if (liveData.isEmpty) {
// Used to create empty memory log for test.
pseudoData = true;
liveData.add(
HeapSample(
DateTime.now().millisecondsSinceEpoch,
0,
0,
0,
0,
false,
AdbMemoryInfo.empty(),
EventSample.empty(),
RasterCache.empty(),
),
);
}

final jsonPayload = SamplesMemoryJson.encodeList(liveData);
if (kDebugMode) {
// TODO(terry): Remove this check add a unit test instead.
// Reload the file just created and validate that the saved data matches
// the live data.
final memoryJson = SamplesMemoryJson.decode(argJsonString: jsonPayload);
assert(memoryJson.isMatchedVersion);
assert(memoryJson.isMemoryPayload);
assert(memoryJson.data.length == liveData.length);
}

_fs.writeStringToFile(_memoryLogFilename, jsonPayload, isMemory: true);

if (pseudoData) liveData.clear();

return [_fs.exportDirectoryName(isMemory: true), _memoryLogFilename];
}

/// Return a list of offline memory logs filenames in the /tmp directory
/// that are available to open.
List<String> offlineFiles() {
final memoryLogs = _fs.list(
prefix: MemoryController.logFilenamePrefix,
isMemory: true,
);

// Sort by newest file top-most (DateTime is in the filename).

memoryLogs.sort((a, b) => b.compareTo(a));

return memoryLogs;
}

/// Load the memory profile data from a saved memory log file.
@visibleForTesting
Future<void> loadOffline(String filename) async {
final jsonPayload = _fs.readStringFromFile(filename, isMemory: true)!;

final memoryJson = SamplesMemoryJson.decode(argJsonString: jsonPayload);

if (!memoryJson.isMatchedVersion) {
final e =
'Error loading file $filename version ${memoryJson.payloadVersion}';
_log.warning(e);
throw OfflineFileException(e);
}

assert(memoryJson.isMemoryPayload);

controller.offline = true;
controller.memoryTimeline.offlineData.clear();
controller.memoryTimeline.offlineData.addAll(memoryJson.data);
}

@visibleForTesting
bool removeOfflineFile(String filename) =>
_fs.deleteFile(filename, isMemory: true);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import 'package:devtools_shared/devtools_shared.dart';
import 'package:logging/logging.dart';
import 'package:vm_service/vm_service.dart';

import '../../shared/globals.dart';
import '../../shared/utils.dart';
import '../../../../shared/globals.dart';
import '../../../../shared/utils.dart';
import '../../shared/primitives/memory_timeline.dart';
import 'memory_controller.dart';
import 'shared/primitives/memory_timeline.dart';

final _log = Logger('memory_protocol');

Expand Down
Loading

0 comments on commit 54111ea

Please sign in to comment.