From 85a45e5fd7cdf5946da19611dd18513bc28b22bb Mon Sep 17 00:00:00 2001 From: Kenzie Davisson <43759233+kenzieschmoll@users.noreply.github.com> Date: Thu, 10 Mar 2022 15:08:22 -0800 Subject: [PATCH] Migrate screens/profiler code to null safety (#3829) --- .../lib/src/charts/flame_chart.dart | 12 +- .../performance/performance_controller.dart | 6 +- .../profiler/cpu_profile_bottom_up.dart | 6 +- .../profiler/cpu_profile_call_tree.dart | 6 +- .../screens/profiler/cpu_profile_columns.dart | 6 +- .../profiler/cpu_profile_controller.dart | 91 ++++---- .../profiler/cpu_profile_flame_chart.dart | 42 ++-- .../screens/profiler/cpu_profile_model.dart | 219 +++++++++--------- .../screens/profiler/cpu_profile_service.dart | 20 +- .../profiler/cpu_profile_transformer.dart | 51 ++-- .../src/screens/profiler/cpu_profiler.dart | 84 ++++--- .../src/screens/profiler/profiler_screen.dart | 44 ++-- .../profiler/profiler_screen_controller.dart | 15 +- .../isolate_statistics_view_controller.dart | 2 +- .../lib/src/ui/vm_flag_widgets.dart | 4 +- .../test/cpu_profile_model_test.dart | 2 + .../test/cpu_profile_transformer_test.dart | 11 +- .../test/cpu_profiler_controller_test.dart | 41 +++- .../devtools_app/test/cpu_profiler_test.dart | 35 +-- .../test/test_data/cpu_profile_test_data.dart | 11 +- 20 files changed, 356 insertions(+), 352 deletions(-) diff --git a/packages/devtools_app/lib/src/charts/flame_chart.dart b/packages/devtools_app/lib/src/charts/flame_chart.dart index 948493517aa..b47bf57a26e 100644 --- a/packages/devtools_app/lib/src/charts/flame_chart.dart +++ b/packages/devtools_app/lib/src/charts/flame_chart.dart @@ -86,7 +86,7 @@ abstract class FlameChart extends StatefulWidget { final double endInset; - final ValueListenable selectionNotifier; + final ValueListenable selectionNotifier; final ValueListenable>? searchMatchesNotifier; @@ -238,7 +238,7 @@ abstract class FlameChartState, + selectionNotifier: widget.selectionNotifier as ValueListenable, searchMatchesNotifier: widget.searchMatchesNotifier as ValueListenable>?, activeSearchMatchNotifier: @@ -664,10 +664,10 @@ class ScrollingFlameChartRowState> selected = widget.selectionNotifier.value; addAutoDisposeListener(widget.selectionNotifier, () { - final containsPreviousSelected = _nodeData.contains(selected); - final containsNewSelected = - _nodeData.contains(widget.selectionNotifier.value); + final containsPreviousSelected = + selected != null && _nodeData.contains(selected); selected = widget.selectionNotifier.value; + final containsNewSelected = _nodeData.contains(selected); // We only want to rebuild the row if it contains the previous or new // selected node. if (containsPreviousSelected || containsNewSelected) { diff --git a/packages/devtools_app/lib/src/screens/performance/performance_controller.dart b/packages/devtools_app/lib/src/screens/performance/performance_controller.dart index 465517d302e..d18cc34982b 100644 --- a/packages/devtools_app/lib/src/screens/performance/performance_controller.dart +++ b/packages/devtools_app/lib/src/screens/performance/performance_controller.dart @@ -750,8 +750,10 @@ class PerformanceController extends DisposableController ); await processTraceEvents(traceEvents); if (data.cpuProfileData != null) { - await cpuProfilerController.transformer - .processData(offlinePerformanceData.cpuProfileData); + await cpuProfilerController.transformer.processData( + offlinePerformanceData.cpuProfileData, + processId: 'process offline data', + ); } offlinePerformanceData.frames.forEach(_assignEventsToFrame); diff --git a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_bottom_up.dart b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_bottom_up.dart index 79c3f502349..41088294721 100644 --- a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_bottom_up.dart +++ b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_bottom_up.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart=2.9 - import 'package:flutter/material.dart'; import '../../primitives/utils.dart'; @@ -14,7 +12,7 @@ import 'cpu_profile_model.dart'; /// A table of the CPU's bottom-up call tree. class CpuBottomUpTable extends StatelessWidget { - factory CpuBottomUpTable(List bottomUpRoots, {Key key}) { + factory CpuBottomUpTable(List bottomUpRoots, {Key? key}) { final treeColumn = MethodNameColumn(); final startingSortColumn = SelfTimeColumn(titleTooltip: selfTimeTooltip); final columns = List>.unmodifiable([ @@ -33,7 +31,7 @@ class CpuBottomUpTable extends StatelessWidget { } const CpuBottomUpTable._( - Key key, + Key? key, this.bottomUpRoots, this.treeColumn, this.sortColumn, diff --git a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_call_tree.dart b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_call_tree.dart index 5132964fe89..f477ffa8107 100644 --- a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_call_tree.dart +++ b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_call_tree.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart=2.9 - import 'package:flutter/material.dart'; import '../../primitives/utils.dart'; @@ -14,7 +12,7 @@ import 'cpu_profile_model.dart'; /// A table of the CPU's top-down call tree. class CpuCallTreeTable extends StatelessWidget { - factory CpuCallTreeTable(List dataRoots, {Key key}) { + factory CpuCallTreeTable(List dataRoots, {Key? key}) { final treeColumn = MethodNameColumn(); final startingSortColumn = TotalTimeColumn(titleTooltip: totalTimeTooltip); final columns = List>.unmodifiable([ @@ -33,7 +31,7 @@ class CpuCallTreeTable extends StatelessWidget { } const CpuCallTreeTable._( - Key key, + Key? key, this.dataRoots, this.treeColumn, this.sortColumn, diff --git a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_columns.dart b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_columns.dart index c6e935f6614..e011ba24fbc 100644 --- a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_columns.dart +++ b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_columns.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart=2.9 - import '../../primitives/utils.dart'; import '../../shared/table_data.dart'; import '../../shared/utils.dart'; @@ -12,7 +10,7 @@ import 'cpu_profile_model.dart'; const _timeColumnWidthPx = 180.0; class SelfTimeColumn extends ColumnData { - SelfTimeColumn({String titleTooltip}) + SelfTimeColumn({String? titleTooltip}) : super( 'Self Time', titleTooltip: titleTooltip, @@ -46,7 +44,7 @@ class SelfTimeColumn extends ColumnData { } class TotalTimeColumn extends ColumnData { - TotalTimeColumn({String titleTooltip}) + TotalTimeColumn({String? titleTooltip}) : super( 'Total Time', titleTooltip: titleTooltip, diff --git a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_controller.dart b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_controller.dart index 0ffb5f59b54..fedd155cb15 100644 --- a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_controller.dart +++ b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_controller.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart=2.9 - import 'dart:async'; import 'package:flutter/foundation.dart'; @@ -56,12 +54,12 @@ class CpuProfilerController static CpuProfileData emptyAppStartUpProfile = CpuProfileData.empty(); /// The analytics screen id for which this controller is active. - final String analyticsScreenId; + final String? analyticsScreenId; /// The store of cached CPU profiles for the currently selected isolate. CpuProfileStore get cpuProfileStore => _cpuProfileStoreByIsolateId.putIfAbsent( - serviceManager.isolateManager.selectedIsolate.value?.id, + serviceManager.isolateManager.selectedIsolate.value?.id ?? '', () => CpuProfileStore(), ); @@ -69,24 +67,26 @@ class CpuProfilerController final _cpuProfileStoreByIsolateId = {}; /// Notifies that new cpu profile data is available. - ValueListenable get dataNotifier => _dataNotifier; - final _dataNotifier = ValueNotifier(baseStateCpuProfileData); + ValueListenable get dataNotifier => _dataNotifier; + final _dataNotifier = ValueNotifier(baseStateCpuProfileData); /// Notifies that CPU profile data is currently being processed. ValueListenable get processingNotifier => _processingNotifier; final _processingNotifier = ValueNotifier(false); /// Notifies that a cpu stack frame was selected. - ValueListenable get selectedCpuStackFrameNotifier => + ValueListenable get selectedCpuStackFrameNotifier => _selectedCpuStackFrameNotifier; - final _selectedCpuStackFrameNotifier = ValueNotifier(null); + final _selectedCpuStackFrameNotifier = ValueNotifier(null); /// Notifies that a user tag filter is set on the CPU profile flame chart. ValueListenable get userTagFilter => _userTagFilter; final _userTagFilter = ValueNotifier(userTagNone); Iterable get userTags => - cpuProfileStore.lookupProfile(label: userTagNone)?.userTags ?? const []; + cpuProfileStore.lookupProfile(label: userTagNone)?.userTags + as Iterable? ?? + const []; bool get isToggleFilterActive => toggleFilters.any((filter) => filter.enabled.value); @@ -113,7 +113,7 @@ class CpuProfilerController ), ]; - List> _toggleFilters; + List>? _toggleFilters; int selectedProfilerTabIndex = 0; @@ -124,10 +124,10 @@ class CpuProfilerController final transformer = CpuProfileTransformer(); /// Notifies that the vm profiler flag has changed. - ValueNotifier get profilerFlagNotifier => + ValueNotifier? get profilerFlagNotifier => serviceManager.vmFlagManager.flag(vm_flags.profiler); - ValueNotifier get profileGranularityFlagNotifier => + ValueNotifier? get profileGranularityFlagNotifier => serviceManager.vmFlagManager.flag(vm_flags.profilePeriod); /// Whether the profiler is enabled. @@ -137,16 +137,16 @@ class CpuProfilerController /// should listen to [profilerFlagNotifier]. bool get profilerEnabled => offlineController.offlineMode.value ? true - : profilerFlagNotifier.value.valueAsString == 'true'; + : profilerFlagNotifier?.value.valueAsString == 'true'; Future enableCpuProfiler() { - return serviceManager.service.enableCpuProfiler(); + return serviceManager.service!.enableCpuProfiler(); } Future pullAndProcessProfile({ - @required int startMicros, - @required int extentMicros, - String processId, + required int startMicros, + required int extentMicros, + required String processId, }) async { if (!profilerEnabled) return; assert(_dataNotifier.value != null); @@ -161,7 +161,7 @@ class CpuProfilerController Future pullAndProcessHelper() async { // TODO(kenz): add a cancel button to the processing UI in case pulling a // large payload from the vm service takes a long time. - cpuProfileData = await serviceManager.service.getCpuProfile( + cpuProfileData = await serviceManager.service!.getCpuProfile( startMicros: startMicros, extentMicros: extentMicros, ); @@ -178,7 +178,7 @@ class CpuProfilerController // Pull and process cpu profile data [pullAndProcessHelper] and time the // operation for analytics. await ga.timeAsync( - analyticsScreenId, + analyticsScreenId!, analytics_constants.cpuProfileProcessingTime, asyncOperation: pullAndProcessHelper, screenMetricsProvider: () => ProfilerScreenMetrics( @@ -202,10 +202,10 @@ class CpuProfilerController /// original data, where no user tag filter has been applied. Future processAndSetData( CpuProfileData cpuProfileData, { - @required String processId, - @required bool storeAsUserTagNone, - @required bool shouldApplyFilters, - @required bool shouldRefreshSearchMatches, + required String processId, + required bool storeAsUserTagNone, + required bool shouldApplyFilters, + required bool shouldRefreshSearchMatches, }) async { _processingNotifier.value = true; _dataNotifier.value = null; @@ -248,10 +248,10 @@ class CpuProfilerController String search, { bool searchPreviousMatches = false, }) { - if (search?.isEmpty ?? true) return []; + if (search.isEmpty) return []; final regexSearch = RegExp(search, caseSensitive: false); final matches = []; - final currentStackFrames = _dataNotifier.value.stackFrames.values; + final currentStackFrames = _dataNotifier.value!.stackFrames.values; for (final frame in currentStackFrames) { if (frame.name.caseInsensitiveContains(regexSearch) || frame.processedUrl.caseInsensitiveContains(regexSearch)) { @@ -307,7 +307,7 @@ class CpuProfilerController label: appStartUpUserTag, ); if (appStartUpProfile == null) { - final cpuProfile = await serviceManager.service.getCpuProfile( + final cpuProfile = await serviceManager.service!.getCpuProfile( startMicros: 0, // Using [maxJsInt] as [extentMicros] for the getCpuProfile requests will // give us all cpu samples we have available. @@ -346,7 +346,10 @@ class CpuProfilerController } if (!appStartUpProfile.processed) { - await transformer.processData(appStartUpProfile); + await transformer.processData( + appStartUpProfile, + processId: 'appStartUpProfile', + ); } _processingNotifier.value = false; @@ -357,7 +360,7 @@ class CpuProfilerController // unprocessed data to avoid processing the data twice. void loadProcessedData( CpuProfileData data, { - @required bool storeAsUserTagNone, + required bool storeAsUserTagNone, }) { assert(data.processed); _dataNotifier.value = data; @@ -378,8 +381,7 @@ class CpuProfilerController // In the event of an error, reset the data to the original CPU profile. final filteredOriginalData = cpuProfileStore.lookupProfile( label: _wrapWithFilterSuffix(userTagNone), - ); - assert(filteredOriginalData != null); + )!; _dataNotifier.value = filteredOriginalData; throw Exception('Error loading data with tag "$tag": ${e.toString()}'); } finally { @@ -388,45 +390,52 @@ class CpuProfilerController } Future processDataForTag(String tag) async { + final profileLabel = _wrapWithFilterSuffix(tag); final filteredDataForTag = cpuProfileStore.lookupProfile( - label: _wrapWithFilterSuffix(tag), + label: profileLabel, ); if (filteredDataForTag != null) { if (!filteredDataForTag.processed) { - await transformer.processData(filteredDataForTag); + await transformer.processData( + filteredDataForTag, + processId: profileLabel, + ); } return filteredDataForTag; } var data = cpuProfileStore.lookupProfile(label: tag); if (data == null) { - final fullData = cpuProfileStore.lookupProfile(label: userTagNone); + final fullData = cpuProfileStore.lookupProfile(label: userTagNone)!; data = CpuProfileData.fromUserTag(fullData, tag); cpuProfileStore.storeProfile(data, label: tag); } data = applyToggleFilters(data); if (!data.processed) { - await transformer.processData(data); + await transformer.processData( + data, + processId: 'data with toggle filters applied', + ); } cpuProfileStore.storeProfile(data, label: _wrapWithFilterSuffix(tag)); return data; } - void selectCpuStackFrame(CpuStackFrame stackFrame) { - if (stackFrame == dataNotifier.value.selectedStackFrame) return; - dataNotifier.value.selectedStackFrame = stackFrame; + void selectCpuStackFrame(CpuStackFrame? stackFrame) { + if (stackFrame == dataNotifier.value!.selectedStackFrame) return; + dataNotifier.value!.selectedStackFrame = stackFrame; _selectedCpuStackFrameNotifier.value = stackFrame; } Future clear() async { reset(); cpuProfileStore.clear(); - await serviceManager.service.clearSamples(); + await serviceManager.service!.clearSamples(); } - void reset({CpuProfileData data}) { + void reset({CpuProfileData? data}) { _selectedCpuStackFrameNotifier.value = null; _dataNotifier.value = data ?? baseStateCpuProfileData; _processingNotifier.value = false; @@ -454,7 +463,7 @@ class CpuProfilerController var filteredData = cpuProfileStore.lookupProfile(label: dataLabel); if (filteredData == null) { final originalData = - cpuProfileStore.lookupProfile(label: _userTagFilter.value); + cpuProfileStore.lookupProfile(label: _userTagFilter.value)!; filteredData = _filterData(originalData, filter); cpuProfileStore.storeProfile(filteredData, label: dataLabel); } @@ -474,7 +483,7 @@ class CpuProfilerController ) { final filterCallback = (CpuStackFrame stackFrame) { var shouldInclude = true; - for (final toggleFilter in filter.toggleFilters) { + for (final toggleFilter in filter.toggleFilters!) { if (toggleFilter.enabled.value) { shouldInclude = shouldInclude && toggleFilter.includeCallback(stackFrame); diff --git a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_flame_chart.dart b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_flame_chart.dart index 1e44ce596b5..40214d6ed5e 100644 --- a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_flame_chart.dart +++ b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_flame_chart.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart=2.9 - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -13,19 +11,19 @@ import '../../ui/utils.dart'; import 'cpu_profile_controller.dart'; import 'cpu_profile_model.dart'; -class CpuProfileFlameChart extends FlameChart { +class CpuProfileFlameChart extends FlameChart { CpuProfileFlameChart({ - @required CpuProfileData data, - @required this.controller, - @required double width, - @required double height, - @required ValueListenable selectionNotifier, - @required ValueListenable> searchMatchesNotifier, - @required ValueListenable activeSearchMatchNotifier, - @required Function(CpuStackFrame stackFrame) onDataSelected, + required CpuProfileData data, + required this.controller, + required double width, + required double height, + required ValueListenable selectionNotifier, + required ValueListenable> searchMatchesNotifier, + required ValueListenable activeSearchMatchNotifier, + required void Function(CpuStackFrame? stackFrame) onDataSelected, }) : super( data, - time: data.profileMetaData.time, + time: data.profileMetaData.time!, containerWidth: width, containerHeight: height, startInset: sideInsetSmall, @@ -46,14 +44,16 @@ class _CpuProfileFlameChartState extends FlameChartState { static const stackFramePadding = 1; - final Map stackFrameLefts = {}; + final stackFrameLefts = {}; @override void initFlameChartElements() { super.initFlameChartElements(); - expandRows(widget.data.cpuProfileRoot.depth + - rowOffsetForTopPadding + - FlameChart.rowOffsetForBottomPadding); + expandRows( + widget.data.cpuProfileRoot.depth + + rowOffsetForTopPadding + + FlameChart.rowOffsetForBottomPadding, + ); void createChartNodes(CpuStackFrame stackFrame, int row) { final double width = @@ -128,13 +128,13 @@ class _CpuProfileFlameChartState @override double startXForData(CpuStackFrame data) { - final x = stackFrameLefts[data.id] - widget.startInset; + final x = stackFrameLefts[data.id]! - widget.startInset; return x * currentZoom; } double startingLeftForStackFrame(CpuStackFrame stackFrame) { - final CpuStackFrame parent = stackFrame.parent; - double left; + final CpuStackFrame? parent = stackFrame.parent; + late double left; if (parent == null) { left = widget.startInset; } else { @@ -142,13 +142,13 @@ class _CpuProfileFlameChartState if (stackFrameIndex == 0) { // This is the first child of parent. [left] should equal the left // value of [stackFrame]'s parent. - left = stackFrameLefts[parent.id]; + left = stackFrameLefts[parent.id]!; } else { assert(stackFrameIndex != -1); // [stackFrame] is not the first child of its parent. [left] should // equal the right value of its previous sibling. final CpuStackFrame previous = parent.children[stackFrameIndex - 1]; - left = stackFrameLefts[previous.id] + + left = stackFrameLefts[previous.id]! + (widget.startingContentWidth * previous.totalTimeRatio); } } diff --git a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_model.dart b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_model.dart index 5ded4ef3464..d1ee018ff09 100644 --- a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_model.dart +++ b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_model.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart=2.9 - import 'dart:collection'; import 'dart:convert'; @@ -20,9 +18,9 @@ import 'cpu_profile_transformer.dart'; /// Data model for DevTools CPU profile. class CpuProfileData { CpuProfileData._({ - @required this.stackFrames, - @required this.cpuSamples, - @required this.profileMetaData, + required this.stackFrames, + required this.cpuSamples, + required this.profileMetaData, }) { _cpuProfileRoot = CpuStackFrame._( id: rootId, @@ -95,7 +93,7 @@ class CpuProfileData { // frame id for a cpu sample within [subTimeRange]. final subSamples = superProfile.cpuSamples .where((sample) => subTimeRange - .contains(Duration(microseconds: sample.timestampMicros))) + .contains(Duration(microseconds: sample.timestampMicros!))) .toList(); // Use a SplayTreeMap so that map iteration will be in sorted key order. @@ -104,14 +102,13 @@ class CpuProfileData { final SplayTreeMap subStackFrames = SplayTreeMap(stackFrameIdCompare); for (final sample in subSamples) { - final leafFrame = superProfile.stackFrames[sample.leafId]; + final leafFrame = superProfile.stackFrames[sample.leafId]!; subStackFrames[sample.leafId] = leafFrame; // Add leaf frame's ancestors. - String parentId = leafFrame.parentId; + String? parentId = leafFrame.parentId; while (parentId != null && parentId != rootId) { - final parentFrame = superProfile.stackFrames[parentId]; - assert(parentFrame != null); + final parentFrame = superProfile.stackFrames[parentId]!; subStackFrames[parentId] = parentFrame; parentId = parentFrame.parentId; } @@ -138,23 +135,23 @@ class CpuProfileData { return CpuProfileData.empty(); } - final samplesForTag = originalData.cpuSamples + final samplesWithTag = originalData.cpuSamples .where((sample) => sample.userTag == tag) .toList(); - assert(samplesForTag.isNotEmpty); + assert(samplesWithTag.isNotEmpty); // Use a SplayTreeMap so that map iteration will be in sorted key order. // This keeps the visualization of the profile as consistent as possible // when applying filters. - final SplayTreeMap stackFramesForTag = + final SplayTreeMap stackFramesWithTag = SplayTreeMap(stackFrameIdCompare); - for (final sample in samplesForTag) { - var currentId = sample.leafId; + for (final sample in samplesWithTag) { + String? currentId = sample.leafId; var currentStackFrame = originalData.stackFrames[currentId]; while (currentStackFrame != null) { - stackFramesForTag[currentId] = currentStackFrame.shallowCopy( + stackFramesWithTag[currentId!] = currentStackFrame.shallowCopy( copySampleCountsAndTags: false, ); final parentId = currentStackFrame.parentId; @@ -165,10 +162,10 @@ class CpuProfileData { } } - final originalTime = originalData.profileMetaData.time.duration; + final originalTime = originalData.profileMetaData.time!.duration; final microsPerSample = originalTime.inMicroseconds / originalData.profileMetaData.sampleCount; - final newSampleCount = samplesForTag.length; + final newSampleCount = samplesWithTag.length; final metaData = originalData.profileMetaData.copyWith( sampleCount: newSampleCount, // The start time is zero because only `TimeRange.duration` will matter @@ -182,8 +179,8 @@ class CpuProfileData { ); return CpuProfileData._( - stackFrames: stackFramesForTag, - cpuSamples: samplesForTag, + stackFrames: stackFramesWithTag, + cpuSamples: samplesWithTag, profileMetaData: metaData, ); } @@ -211,15 +208,14 @@ class CpuProfileData { ), ); } else if (stackFrame.parentId != CpuProfileData.rootId) { - final parent = originalData.stackFrames[stackFrame.parentId]; - assert(parent != null); + final parent = originalData.stackFrames[stackFrame.parentId]!; includeSampleOrWalkUp(sample, sampleJson, parent); } } for (final sample in originalData.cpuSamples) { final sampleJson = Map.from(sample.json); - final leafStackFrame = originalData.stackFrames[sample.leafId]; + final leafStackFrame = originalData.stackFrames[sample.leafId]!; includeSampleOrWalkUp(sample, sampleJson, leafStackFrame); } @@ -229,14 +225,13 @@ class CpuProfileData { final SplayTreeMap filteredStackFrames = SplayTreeMap(stackFrameIdCompare); - String filteredParentStackFrameId(CpuStackFrame candidateParentFrame) { + String? filteredParentStackFrameId(CpuStackFrame? candidateParentFrame) { if (candidateParentFrame == null) return null; if (includeFilter(candidateParentFrame)) { return candidateParentFrame.id; } else if (candidateParentFrame.parentId != CpuProfileData.rootId) { - final parent = originalData.stackFrames[candidateParentFrame.parentId]; - assert(parent != null); + final parent = originalData.stackFrames[candidateParentFrame.parentId]!; return filteredParentStackFrameId(parent); } return null; @@ -251,17 +246,16 @@ class CpuProfileData { parentId: filteredParentId, ); if (filteredParentId != null) { - walkAndFilter(originalData.stackFrames[filteredParentId]); + walkAndFilter(originalData.stackFrames[filteredParentId]!); } } else if (stackFrame.parentId != CpuProfileData.rootId) { - final parent = originalData.stackFrames[stackFrame.parentId]; - assert(parent != null); + final parent = originalData.stackFrames[stackFrame.parentId]!; walkAndFilter(parent); } } for (final sample in filteredCpuSamples) { - final leafStackFrame = originalData.stackFrames[sample.leafId]; + final leafStackFrame = originalData.stackFrames[sample.leafId]!; walkAndFilter(leafStackFrame); } @@ -313,7 +307,7 @@ class CpuProfileData { return _callTreeRoots ??= [_cpuProfileRoot.deepCopy()]; } - List _callTreeRoots; + List? _callTreeRoots; List get bottomUpRoots { if (!processed) return []; @@ -321,36 +315,37 @@ class CpuProfileData { BottomUpProfileTransformer.processData(_cpuProfileRoot); } - List _bottomUpRoots; + List? _bottomUpRoots; CpuStackFrame get cpuProfileRoot => _cpuProfileRoot; - Iterable get userTags { + Iterable get userTags { if (_userTags != null) { - return _userTags; + return _userTags!; } - final tags = {}; + final tags = {}; for (final cpuSample in cpuSamples) { tags.add(cpuSample.userTag); } - return _userTags = tags; + _userTags = tags; + return _userTags!; } - Iterable _userTags; + Iterable? _userTags; - CpuStackFrame _cpuProfileRoot; + late final CpuStackFrame _cpuProfileRoot; - CpuStackFrame selectedStackFrame; + CpuStackFrame? selectedStackFrame; - Map get toJson => { + Map get toJson => { 'type': '_CpuProfileTimeline', samplePeriodKey: profileMetaData.samplePeriod, sampleCountKey: profileMetaData.sampleCount, stackDepthKey: profileMetaData.stackDepth, - if (profileMetaData?.time?.start != null) - timeOriginKey: profileMetaData.time.start.inMicroseconds, - if (profileMetaData?.time?.duration != null) - timeExtentKey: profileMetaData.time.duration.inMicroseconds, + if (profileMetaData.time?.start != null) + timeOriginKey: profileMetaData.time!.start!.inMicroseconds, + if (profileMetaData.time?.duration != null) + timeExtentKey: profileMetaData.time!.duration.inMicroseconds, stackFramesKey: stackFramesJson, traceEventsKey: cpuSamples.map((sample) => sample.toJson).toList(), }; @@ -369,10 +364,10 @@ class CpuProfileData { class CpuProfileMetaData { CpuProfileMetaData({ - @required this.sampleCount, - @required this.samplePeriod, - @required this.stackDepth, - @required this.time, + required this.sampleCount, + required this.samplePeriod, + required this.stackDepth, + required this.time, }); final int sampleCount; @@ -381,13 +376,13 @@ class CpuProfileMetaData { final int stackDepth; - final TimeRange time; + final TimeRange? time; CpuProfileMetaData copyWith({ - int sampleCount, - int samplePeriod, - int stackDepth, - TimeRange time, + int? sampleCount, + int? samplePeriod, + int? stackDepth, + TimeRange? time, }) { return CpuProfileMetaData( sampleCount: sampleCount ?? this.sampleCount, @@ -400,9 +395,9 @@ class CpuProfileMetaData { class CpuSample extends TraceEvent { CpuSample({ - @required this.leafId, + required this.leafId, this.userTag, - Map traceJson, + required Map traceJson, }) : super(traceJson); factory CpuSample.parse(Map traceJson) { @@ -419,9 +414,9 @@ class CpuSample extends TraceEvent { final String leafId; - final String userTag; + final String? userTag; - Map get toJson { + Map get toJson { // [leafId] is the source of truth for the leaf id of this sample. super.json[CpuProfileData.stackFrameIdKey] = leafId; return super.json; @@ -434,16 +429,15 @@ class CpuStackFrame extends TreeNode TreeDataSearchStateMixin, FlameChartDataMixin { factory CpuStackFrame({ - @required String id, - @required String name, - @required String verboseName, - @required String category, - @required String rawUrl, - @required int sourceLine, - @required String parentId, - @required CpuProfileMetaData profileMetaData, + required String id, + required String name, + required String? verboseName, + required String? category, + required String rawUrl, + required int? sourceLine, + required String parentId, + required CpuProfileMetaData profileMetaData, }) { - assert(rawUrl != null); return CpuStackFrame._( id: id, name: name, @@ -459,15 +453,15 @@ class CpuStackFrame extends TreeNode } CpuStackFrame._({ - @required this.id, - @required this.name, - @required this.verboseName, - @required this.category, - @required this.rawUrl, - @required this.processedUrl, - @required this.sourceLine, - @required this.parentId, - @required this.profileMetaData, + required this.id, + required this.name, + required this.verboseName, + required this.category, + required this.rawUrl, + required this.processedUrl, + required this.sourceLine, + required this.parentId, + required this.profileMetaData, }); /// Prefix for packages from the core Dart libraries. @@ -486,17 +480,17 @@ class CpuStackFrame extends TreeNode final String name; - final String verboseName; + final String? verboseName; - final String category; + final String? category; final String rawUrl; final String processedUrl; - final int sourceLine; + final int? sourceLine; - final String parentId; + final String? parentId; final CpuProfileMetaData profileMetaData; @@ -504,20 +498,20 @@ class CpuStackFrame extends TreeNode processedUrl.isEmpty && !name.startsWith(flutterEnginePrefix); - bool _isNative; + bool? _isNative; bool get isDartCore => _isDartCore ??= processedUrl.startsWith(dartPackagePrefix) && !processedUrl.startsWith(dartUiPrefix); - bool _isDartCore; + bool? _isDartCore; bool get isFlutterCore => _isFlutterCore ??= processedUrl.startsWith(flutterPackagePrefix) || name.startsWith(flutterEnginePrefix) || processedUrl.startsWith(dartUiPrefix); - bool _isFlutterCore; + bool? _isFlutterCore; Iterable get userTags => _userTagSampleCount.keys; @@ -528,12 +522,11 @@ class CpuStackFrame extends TreeNode final _userTagSampleCount = {}; void incrementTagSampleCount(String userTag, {int increment = 1}) { - assert(userTag != null); final currentCount = _userTagSampleCount.putIfAbsent(userTag, () => 0); _userTagSampleCount[userTag] = currentCount + increment; if (parent != null) { - parent.incrementTagSampleCount(userTag); + parent!.incrementTagSampleCount(userTag); } } @@ -541,35 +534,36 @@ class CpuStackFrame extends TreeNode int exclusiveSampleCount = 0; int get inclusiveSampleCount => - _inclusiveSampleCount ?? _calculateInclusiveSampleCount(); + _inclusiveSampleCount ??= _calculateInclusiveSampleCount(); /// How many cpu samples this frame is included in. - int _inclusiveSampleCount; - set inclusiveSampleCount(int count) => _inclusiveSampleCount = count; + int? _inclusiveSampleCount; + + set inclusiveSampleCount(int? count) => _inclusiveSampleCount = count; double get totalTimeRatio => _totalTimeRatio ??= safeDivide(inclusiveSampleCount, profileMetaData.sampleCount); - double _totalTimeRatio; + double? _totalTimeRatio; Duration get totalTime => _totalTime ??= Duration( microseconds: - (totalTimeRatio * profileMetaData.time.duration.inMicroseconds) + (totalTimeRatio * profileMetaData.time!.duration.inMicroseconds) .round()); - Duration _totalTime; + Duration? _totalTime; double get selfTimeRatio => _selfTimeRatio ??= safeDivide(exclusiveSampleCount, profileMetaData.sampleCount); - double _selfTimeRatio; + double? _selfTimeRatio; Duration get selfTime => _selfTime ??= Duration( microseconds: - (selfTimeRatio * profileMetaData.time.duration.inMicroseconds) + (selfTimeRatio * profileMetaData.time!.duration.inMicroseconds) .round()); - Duration _selfTime; + Duration? _selfTime; @override String get tooltip { @@ -598,18 +592,18 @@ class CpuStackFrame extends TreeNode count += child.inclusiveSampleCount; } _inclusiveSampleCount = count; - return _inclusiveSampleCount; + return _inclusiveSampleCount!; } @override CpuStackFrame shallowCopy({ - String id, - String name, - String verboseName, - String category, - String url, - String parentId, - CpuProfileMetaData profileMetaData, + String? id, + String? name, + String? verboseName, + String? category, + String? url, + String? parentId, + CpuProfileMetaData? profileMetaData, bool copySampleCountsAndTags = true, bool resetInclusiveSampleCount = true, }) { @@ -686,11 +680,9 @@ class CpuStackFrame extends TreeNode String toString() { final buf = StringBuffer(); buf.write('$name '); - if (totalTime != null) { - // TODO(kenz): use a number of fractionDigits that better matches the - // resolution of the stack frame. - buf.write('- ${msText(totalTime, fractionDigits: 2)} '); - } + // TODO(kenz): use a number of fractionDigits that better matches the + // resolution of the stack frame. + buf.write('- ${msText(totalTime, fractionDigits: 2)} '); buf.write('($inclusiveSampleCount '); buf.write(inclusiveSampleCount == 1 ? 'sample' : 'samples'); buf.write(', ${percent2(totalTimeRatio)})'); @@ -763,14 +755,14 @@ class CpuProfileStore { /// generated, cached in [_profilesByTime] and then returned. This method will /// return null if no profiles are cached for [time] or if a sub profile /// cannot be generated for [time]. - CpuProfileData lookupProfile({String label, TimeRange time}) { + CpuProfileData? lookupProfile({String? label, TimeRange? time}) { assert((label == null) != (time == null)); if (label != null) { return _profilesByLabel[label]; } - if (!time.isWellFormed) return null; + if (!time!.isWellFormed) return null; // If we have a profile for a time range encompassing [time], then we can // generate and cache the profile for [time] without needing to pull data @@ -779,29 +771,28 @@ class CpuProfileStore { return _profilesByTime[time]; } - void storeProfile(CpuProfileData profile, {String label, TimeRange time}) { + void storeProfile(CpuProfileData profile, {String? label, TimeRange? time}) { assert((label == null) != (time == null)); if (label != null) { _profilesByLabel[label] = profile; return; } - _profilesByTime[time] = profile; + _profilesByTime[time!] = profile; } void _maybeGenerateSubProfile(TimeRange time) { if (_profilesByTime.containsKey(time)) return; final encompassingTimeRange = _encompassingTimeRange(time); if (encompassingTimeRange != null) { - final encompassingProfile = _profilesByTime[encompassingTimeRange]; - + final encompassingProfile = _profilesByTime[encompassingTimeRange]!; final subProfile = CpuProfileData.subProfile(encompassingProfile, time); _profilesByTime[time] = subProfile; } } - TimeRange _encompassingTimeRange(TimeRange time) { + TimeRange? _encompassingTimeRange(TimeRange time) { int shortestDurationMicros = maxJsInt; - TimeRange encompassingTimeRange; + TimeRange? encompassingTimeRange; for (final t in _profilesByTime.keys) { // We want to find the shortest encompassing time range for [time]. if (t.containsRange(time) && diff --git a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_service.dart b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_service.dart index 5b486624f08..6093b94ed90 100644 --- a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_service.dart +++ b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_service.dart @@ -2,10 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart=2.9 - -import 'package:flutter/foundation.dart'; - import '../../service/vm_flags.dart' as vm_flags; import '../../service/vm_service_wrapper.dart'; import '../../shared/globals.dart'; @@ -14,26 +10,26 @@ import 'cpu_profile_model.dart'; /// Manages interactions between the Cpu Profiler and the VmService. extension CpuProfilerExtension on VmServiceWrapper { Future getCpuProfile({ - @required int startMicros, - @required int extentMicros, + required int startMicros, + required int extentMicros, }) async { - return await serviceManager.service.getCpuProfileTimeline( - serviceManager.isolateManager.selectedIsolate.value.id, + return await serviceManager.service!.getCpuProfileTimeline( + serviceManager.isolateManager.selectedIsolate.value!.id!, startMicros, extentMicros, ); } Future clearSamples() { - return serviceManager.service.clearCpuSamples( - serviceManager.isolateManager.selectedIsolate.value.id); + return serviceManager.service!.clearCpuSamples( + serviceManager.isolateManager.selectedIsolate.value!.id!); } Future setProfilePeriod(String value) { - return serviceManager.service.setFlag(vm_flags.profilePeriod, value); + return serviceManager.service!.setFlag(vm_flags.profilePeriod, value); } Future enableCpuProfiler() async { - return await serviceManager.service.setFlag(vm_flags.profiler, 'true'); + return await serviceManager.service!.setFlag(vm_flags.profiler, 'true'); } } diff --git a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_transformer.dart b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_transformer.dart index edb487a228a..c4b575355bb 100644 --- a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_transformer.dart +++ b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_transformer.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart=2.9 - import 'dart:math' as math; import 'package:flutter/foundation.dart'; @@ -23,19 +21,19 @@ class CpuProfileTransformer { ValueListenable get progressNotifier => _progressNotifier; final _progressNotifier = ValueNotifier(0.0); - int _stackFramesCount; + late int _stackFramesCount; - List _stackFrameKeys; + List? _stackFrameKeys; - List _stackFrameValues; + List? _stackFrameValues; int _stackFramesProcessed = 0; - String _activeProcessId; + String? _activeProcessId; Future processData( CpuProfileData cpuProfileData, { - String processId, + required String processId, }) async { // Do not process this data if it has already been processed. if (cpuProfileData.processed) return; @@ -44,10 +42,9 @@ class CpuProfileTransformer { reset(); _activeProcessId = processId; - _stackFramesCount = cpuProfileData?.stackFrames?.length ?? 0; - _stackFrameKeys = cpuProfileData?.stackFrames?.keys?.toList() ?? []; - _stackFrameValues = - cpuProfileData?.stackFrames?.values?.toList() ?? []; + _stackFramesCount = cpuProfileData.stackFrames.length; + _stackFrameKeys = cpuProfileData.stackFrames.keys.toList(); + _stackFrameValues = cpuProfileData.stackFrames.values.toList(); // At minimum, process the data in 4 batches to smooth the appearance of // the progress indicator. @@ -59,7 +56,7 @@ class CpuProfileTransformer { // Use batch processing to maintain a responsive UI. while (_stackFramesProcessed < _stackFramesCount) { - _processBatch(batchSize, cpuProfileData, processId); + _processBatch(batchSize, cpuProfileData, processId: processId); _progressNotifier.value = _stackFramesProcessed / _stackFramesCount; // Await a small delay to give the UI thread a chance to update the @@ -91,18 +88,18 @@ class CpuProfileTransformer { void _processBatch( int batchSize, - CpuProfileData cpuProfileData, - String processId, - ) { + CpuProfileData cpuProfileData, { + required String processId, + }) { final batchEnd = math.min(_stackFramesProcessed + batchSize, _stackFramesCount); for (int i = _stackFramesProcessed; i < batchEnd; i++) { if (processId != _activeProcessId) { throw ProcessCancelledException(); } - final key = _stackFrameKeys[i]; - final value = _stackFrameValues[i]; - final stackFrame = cpuProfileData.stackFrames[key]; + final key = _stackFrameKeys![i]; + final value = _stackFrameValues![i]; + final stackFrame = cpuProfileData.stackFrames[key]!; final parent = cpuProfileData.stackFrames[value.parentId]; _processStackFrame(stackFrame, parent, cpuProfileData); _stackFramesProcessed++; @@ -111,7 +108,7 @@ class CpuProfileTransformer { void _processStackFrame( CpuStackFrame stackFrame, - CpuStackFrame parent, + CpuStackFrame? parent, CpuProfileData cpuProfileData, ) { if (parent == null) { @@ -126,18 +123,20 @@ class CpuProfileTransformer { void _setExclusiveSampleCountsAndTags(CpuProfileData cpuProfileData) { for (CpuSample sample in cpuProfileData.cpuSamples) { final leafId = sample.leafId; + final stackFrame = cpuProfileData.stackFrames[leafId]; assert( - cpuProfileData.stackFrames[leafId] != null, + stackFrame != null, 'No StackFrame found for id $leafId. If you see this assertion, please ' 'export the timeline trace and send to kenzieschmoll@google.com. Note: ' 'you must export the timeline immediately after the AssertionError is ' 'thrown.', ); - final stackFrame = cpuProfileData.stackFrames[leafId]; - stackFrame?.exclusiveSampleCount++; - final userTag = sample.userTag; - if (userTag != null) { - stackFrame.incrementTagSampleCount(userTag); + if (stackFrame != null) { + stackFrame.exclusiveSampleCount++; + final userTag = sample.userTag; + if (userTag != null) { + stackFrame.incrementTagSampleCount(userTag); + } } } } @@ -182,7 +181,7 @@ class BottomUpProfileTransformer { @visibleForTesting static List getRoots( CpuStackFrame node, - CpuStackFrame currentBottomUpRoot, + CpuStackFrame? currentBottomUpRoot, List bottomUpRoots, ) { final copy = node.shallowCopy(); diff --git a/packages/devtools_app/lib/src/screens/profiler/cpu_profiler.dart b/packages/devtools_app/lib/src/screens/profiler/cpu_profiler.dart index 79ec11a3ed0..6959c589317 100644 --- a/packages/devtools_app/lib/src/screens/profiler/cpu_profiler.dart +++ b/packages/devtools_app/lib/src/screens/profiler/cpu_profiler.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart=2.9 - import 'dart:developer'; import 'package:flutter/material.dart'; @@ -30,24 +28,24 @@ import 'cpu_profile_model.dart'; class CpuProfiler extends StatefulWidget { CpuProfiler({ - @required this.data, - @required this.controller, + required this.data, + required this.controller, this.searchFieldKey, this.standaloneProfiler = true, this.summaryView, - }) : callTreeRoots = data?.callTreeRoots ?? [], - bottomUpRoots = data?.bottomUpRoots ?? [], + }) : callTreeRoots = data.callTreeRoots, + bottomUpRoots = data.bottomUpRoots, tabs = [ if (summaryView != null) _buildTab(key: summaryTab, tabName: 'Summary'), - if (data != null && !data.isEmpty) ...[ + if (!data.isEmpty) ...[ _buildTab(key: bottomUpTab, tabName: 'Bottom Up'), _buildTab(key: callTreeTab, tabName: 'Call Tree'), _buildTab(key: flameChartTab, tabName: 'CPU Flame Chart'), ], ]; - static DevToolsTab _buildTab({Key key, @required String tabName}) { + static DevToolsTab _buildTab({Key? key, required String tabName}) { return DevToolsTab.create( key: key, tabName: tabName, @@ -63,11 +61,11 @@ class CpuProfiler extends StatefulWidget { final List bottomUpRoots; - final Key searchFieldKey; + final GlobalKey? searchFieldKey; final bool standaloneProfiler; - final Widget summaryView; + final Widget? summaryView; final List tabs; @@ -91,9 +89,11 @@ class _CpuProfilerState extends State TickerProviderStateMixin, AutoDisposeMixin, SearchFieldMixin { - TabController _tabController; + bool _tabControllerInitialized = false; + + late TabController _tabController; - CpuProfileData data; + late CpuProfileData data; @override void initState() { @@ -117,18 +117,21 @@ class _CpuProfilerState extends State @override void dispose() { - _tabController?.removeListener(_onTabChanged); + _tabController.removeListener(_onTabChanged); _tabController.dispose(); super.dispose(); } void _initTabController() { - _tabController?.removeListener(_onTabChanged); - _tabController?.dispose(); + if (_tabControllerInitialized) { + _tabController.removeListener(_onTabChanged); + _tabController.dispose(); + } _tabController = TabController( length: widget.tabs.length, vsync: this, ); + _tabControllerInitialized = true; if (widget.controller.selectedProfilerTabIndex >= _tabController.length) { widget.controller.changeSelectedProfilerTab(0); @@ -151,9 +154,8 @@ class _CpuProfilerState extends State final colorScheme = theme.colorScheme; final currentTab = widget.tabs.isNotEmpty ? widget.tabs[_tabController.index] : null; - final hasData = data != CpuProfilerController.baseStateCpuProfileData && - data != null && - !data.isEmpty; + final hasData = + data != CpuProfilerController.baseStateCpuProfileData && !data.isEmpty; return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -163,14 +165,15 @@ class _CpuProfilerState extends State child: Row( children: [ TabBar( - labelColor: textTheme.bodyText1.color, + labelColor: + textTheme.bodyText1?.color ?? colorScheme.defaultForeground, isScrollable: true, controller: _tabController, tabs: widget.tabs, ), const Spacer(), if (hasData) ...[ - if (currentTab.key != CpuProfiler.summaryTab) ...[ + if (currentTab!.key != CpuProfiler.summaryTab) ...[ FilterButton( onPressed: _showFilterDialog, isFilterActive: widget.controller.isToggleFilterActive, @@ -255,7 +258,11 @@ class _CpuProfilerState extends State ), ), Expanded( - child: _buildCpuProfileDataView(), + child: TabBarView( + physics: defaultTabBarViewPhysics, + controller: _tabController, + children: _buildProfilerViews(), + ), ), ], ); @@ -276,7 +283,7 @@ class _CpuProfilerState extends State height: defaultTextFieldHeight, child: buildSearchField( controller: widget.controller, - searchFieldKey: widget.searchFieldKey, + searchFieldKey: widget.searchFieldKey!, searchFieldEnabled: hasData, shouldRequestFocus: false, supportsNavigation: true, @@ -284,20 +291,6 @@ class _CpuProfilerState extends State ); } - Widget _buildCpuProfileDataView() { - if (data != null) { - return TabBarView( - physics: defaultTabBarViewPhysics, - controller: _tabController, - children: _buildProfilerViews(), - ); - } else { - // If [data] is null, CPU profile data is being processed, so return a - // placeholder. - return const SizedBox(key: CpuProfiler.dataProcessingKey); - } - } - List _buildProfilerViews() { final bottomUp = CpuBottomUpTable(widget.bottomUpRoots); final callTree = CpuCallTreeTable(widget.callTreeRoots); @@ -315,9 +308,10 @@ class _CpuProfilerState extends State ); }, ); + final summaryView = widget.summaryView; // TODO(kenz): make this order configurable. return [ - if (widget.summaryView != null) widget.summaryView, + if (summaryView != null) summaryView, if (!data.isEmpty) ...[ bottomUp, callTree, @@ -341,8 +335,8 @@ class _CpuProfilerState extends State class CpuProfileFilterDialog extends StatelessWidget { CpuProfileFilterDialog({ - @required this.controller, - Key key, + required this.controller, + Key? key, }) : oldToggleFilterValues = List.generate(controller.toggleFilters.length, (index) => controller.toggleFilters[index].enabled.value), super(key: key); @@ -410,7 +404,7 @@ class UserTagDropdown extends StatelessWidget { return ValueListenableBuilder( valueListenable: controller.userTagFilter, builder: (context, userTag, _) { - final userTags = controller.userTags ?? []; + final userTags = controller.userTags; final tooltip = userTags.isNotEmpty ? 'Filter the CPU profile by the given UserTag' : 'No UserTags found for this CPU profile'; @@ -440,16 +434,16 @@ class UserTagDropdown extends StatelessWidget { (userTags.length == 1 && userTags.first == UserTag.defaultTag.label) ? null - : (String tag) => _onUserTagChanged(tag, context), + : (String? tag) => _onUserTagChanged(tag!, context), ), ); }, ); } - DropdownMenuItem _buildMenuItem({ - @required String display, - @required String value, + DropdownMenuItem _buildMenuItem({ + required String display, + required String value, }) { return DropdownMenuItem( value: value, @@ -461,7 +455,7 @@ class UserTagDropdown extends StatelessWidget { try { await controller.loadDataWithTag(newTag); } catch (e) { - Notifications.of(context).push(e.toString()); + Notifications.of(context)!.push(e.toString()); } } } diff --git a/packages/devtools_app/lib/src/screens/profiler/profiler_screen.dart b/packages/devtools_app/lib/src/screens/profiler/profiler_screen.dart index 1c849e00777..59c780a9182 100644 --- a/packages/devtools_app/lib/src/screens/profiler/profiler_screen.dart +++ b/packages/devtools_app/lib/src/screens/profiler/profiler_screen.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart=2.9 - import 'dart:async'; import 'package:flutter/foundation.dart'; @@ -75,7 +73,8 @@ class _ProfilerScreenBodyState extends State with AutoDisposeMixin, OfflineScreenMixin { - ProfilerScreenController controller; + late ProfilerScreenController controller; + bool _controllerInitialized = false; bool recording = false; @@ -96,8 +95,9 @@ class _ProfilerScreenBodyState extends State maybePushDebugModePerformanceMessage(context, ProfilerScreen.id); final newController = Provider.of(context); - if (newController == controller) return; + if (_controllerInitialized && newController == controller) return; controller = newController; + _controllerInitialized = true; cancelListeners(); @@ -138,7 +138,7 @@ class _ProfilerScreenBodyState extends State if (offlineController.offlineMode.value) return _buildProfilerScreenBody(controller); return ValueListenableBuilder( - valueListenable: controller.cpuProfilerController.profilerFlagNotifier, + valueListenable: controller.cpuProfilerController.profilerFlagNotifier!, builder: (context, profilerFlag, _) { return profilerFlag.valueAsString == 'true' ? _buildProfilerScreenBody(controller) @@ -201,7 +201,7 @@ class _ProfilerScreenBodyState extends State ), const SizedBox(height: denseRowSpacing), Expanded( - child: ValueListenableBuilder( + child: ValueListenableBuilder( valueListenable: controller.cpuProfilerController.dataNotifier, builder: (context, cpuProfileData, _) { if (cpuProfileData == @@ -256,7 +256,10 @@ class _ProfilerScreenBodyState extends State @override FutureOr processOfflineData(CpuProfileData offlineData) async { - await controller.cpuProfilerController.transformer.processData(offlineData); + await controller.cpuProfilerController.transformer.processData( + offlineData, + processId: 'offline data processing', + ); controller.cpuProfilerController.loadProcessedData( offlineData, storeAsUserTagNone: true, @@ -271,9 +274,9 @@ class _ProfilerScreenBodyState extends State class ProfilerScreenControls extends StatelessWidget { const ProfilerScreenControls({ - @required this.controller, - @required this.recording, - @required this.processing, + required this.controller, + required this.recording, + required this.processing, }); final ProfilerScreenController controller; @@ -303,8 +306,8 @@ class ProfilerScreenControls extends StatelessWidget { class _PrimaryControls extends StatelessWidget { const _PrimaryControls({ - @required this.controller, - @required this.recording, + required this.controller, + required this.recording, }); static const _primaryControlsMinIncludeTextWidth = 1050.0; @@ -363,8 +366,8 @@ class _PrimaryControls extends StatelessWidget { class _SecondaryControls extends StatelessWidget { const _SecondaryControls({ - @required this.controller, - @required this.profilerBusy, + required this.controller, + required this.profilerBusy, }); static const _secondaryControlsMinScreenWidthForText = 1050.0; @@ -380,7 +383,7 @@ class _SecondaryControls extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - if (serviceManager.connectedApp.isFlutterNativeAppNow) + if (serviceManager.connectedApp!.isFlutterNativeAppNow) IconLabelButton( icon: Icons.timer, label: 'Profile app start up', @@ -419,13 +422,13 @@ class _SecondaryControls extends StatelessWidget { ProfileGranularityDropdown( screenId: ProfilerScreen.id, profileGranularityFlagNotifier: - controller.cpuProfilerController.profileGranularityFlagNotifier, + controller.cpuProfilerController.profileGranularityFlagNotifier!, ), const SizedBox(width: denseSpacing), ExportButton( onPressed: !profilerBusy && controller.cpuProfileData != null && - !controller.cpuProfileData.isEmpty + controller.cpuProfileData?.isEmpty == false ? () { ga.select( analytics_constants.cpuProfiler, @@ -447,12 +450,15 @@ class _SecondaryControls extends StatelessWidget { // download always successful? // TODO(peterdjlee): find a way to push the notification logic into the // export controller. - Notifications.of(context).push(successfulExportMessage(exportedFile)); + Notifications.of(context)!.push(successfulExportMessage(exportedFile)); } } class ProfilerScreenMetrics extends ScreenAnalyticsMetrics { - ProfilerScreenMetrics({this.cpuSampleCount, this.cpuStackDepth}); + ProfilerScreenMetrics({ + required this.cpuSampleCount, + required this.cpuStackDepth, + }); final int cpuSampleCount; final int cpuStackDepth; diff --git a/packages/devtools_app/lib/src/screens/profiler/profiler_screen_controller.dart b/packages/devtools_app/lib/src/screens/profiler/profiler_screen_controller.dart index cdfa86b0a99..f5943029e8f 100644 --- a/packages/devtools_app/lib/src/screens/profiler/profiler_screen_controller.dart +++ b/packages/devtools_app/lib/src/screens/profiler/profiler_screen_controller.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart=2.9 - import 'package:flutter/foundation.dart'; import 'package:vm_service/vm_service.dart'; @@ -24,7 +22,7 @@ class ProfilerScreenController extends DisposableController ProfilerScreenController() { if (!offlineController.offlineMode.value) { allowedError( - serviceManager.service.setProfilePeriod(mediumProfilePeriod), + serviceManager.service!.setProfilePeriod(mediumProfilePeriod), logError: false, ); @@ -40,9 +38,10 @@ class ProfilerScreenController extends DisposableController final _exportController = ExportController(); - CpuProfileData get cpuProfileData => cpuProfilerController.dataNotifier.value; + CpuProfileData? get cpuProfileData => + cpuProfilerController.dataNotifier.value; - final _previousProfileByIsolateId = {}; + final _previousProfileByIsolateId = {}; /// Notifies that a CPU profile is currently being recorded. ValueListenable get recordingNotifier => _recordingNotifier; @@ -51,9 +50,9 @@ class ProfilerScreenController extends DisposableController final int _profileStartMicros = 0; - IsolateRef _currentIsolate; + IsolateRef? _currentIsolate; - void switchToIsolate(IsolateRef ref) { + void switchToIsolate(IsolateRef? ref) { // Store the data for the current isolate. if (_currentIsolate?.id != null) { _previousProfileByIsolateId[_currentIsolate?.id] = @@ -88,7 +87,7 @@ class ProfilerScreenController extends DisposableController /// This method returns the name of the file that was downloaded. String exportData() { final encodedData = - _exportController.encode(ProfilerScreen.id, cpuProfileData.toJson); + _exportController.encode(ProfilerScreen.id, cpuProfileData!.toJson); return _exportController.downloadFile(encodedData); } diff --git a/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics_view_controller.dart b/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics_view_controller.dart index aa75f4d3617..c4b84c49dbd 100644 --- a/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics_view_controller.dart +++ b/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics_view_controller.dart @@ -20,7 +20,7 @@ class IsolateStatisticsViewController extends DisposableController IsolateStatisticsViewController() { // If the CPU profiler is enabled later, refresh the isolate data to get // the tag information. - cpuProfilerController.profilerFlagNotifier.addListener( + cpuProfilerController.profilerFlagNotifier?.addListener( () => refresh(), ); diff --git a/packages/devtools_app/lib/src/ui/vm_flag_widgets.dart b/packages/devtools_app/lib/src/ui/vm_flag_widgets.dart index 63c93bd3ae3..d108fd24b28 100644 --- a/packages/devtools_app/lib/src/ui/vm_flag_widgets.dart +++ b/packages/devtools_app/lib/src/ui/vm_flag_widgets.dart @@ -92,6 +92,8 @@ class ProfileGranularityDropdown extends StatelessWidget { '${analytics_constants.profileGranularityPrefix}' '${ProfileGranularityExtension.fromValue(newValue ?? '').displayShort}', ); - await serviceManager.service.setProfilePeriod(newValue); + await serviceManager.service!.setProfilePeriod( + newValue ?? mediumProfilePeriod, + ); } } diff --git a/packages/devtools_app/test/cpu_profile_model_test.dart b/packages/devtools_app/test/cpu_profile_model_test.dart index 6b63df7433c..b06fc463c04 100644 --- a/packages/devtools_app/test/cpu_profile_model_test.dart +++ b/packages/devtools_app/test/cpu_profile_model_test.dart @@ -4,6 +4,8 @@ // @dart=2.9 +// ignore_for_file: avoid_redundant_argument_values + import 'package:devtools_app/src/primitives/utils.dart'; import 'package:devtools_app/src/screens/profiler/cpu_profile_model.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/devtools_app/test/cpu_profile_transformer_test.dart b/packages/devtools_app/test/cpu_profile_transformer_test.dart index 641a146ef83..9e2d2317da7 100644 --- a/packages/devtools_app/test/cpu_profile_transformer_test.dart +++ b/packages/devtools_app/test/cpu_profile_transformer_test.dart @@ -22,7 +22,10 @@ void main() { test('processData', () async { expect(cpuProfileData.processed, isFalse); - await cpuProfileTransformer.processData(cpuProfileData); + await cpuProfileTransformer.processData( + cpuProfileData, + processId: 'test', + ); expect(cpuProfileData.processed, isTrue); expect( cpuProfileData.cpuProfileRoot.toStringDeep(), @@ -36,8 +39,10 @@ void main() { CpuProfileData.parse(responseWithMissingLeafFrame); expect( () async { - await cpuProfileTransformer - .processData(cpuProfileDataWithMissingLeaf); + await cpuProfileTransformer.processData( + cpuProfileDataWithMissingLeaf, + processId: 'test', + ); }, throwsA(const TypeMatcher()), ); diff --git a/packages/devtools_app/test/cpu_profiler_controller_test.dart b/packages/devtools_app/test/cpu_profiler_controller_test.dart index 54ba78728b1..ce3adedd443 100644 --- a/packages/devtools_app/test/cpu_profiler_controller_test.dart +++ b/packages/devtools_app/test/cpu_profiler_controller_test.dart @@ -31,7 +31,11 @@ void main() { }); Future pullProfileAndSelectFrame() async { - await controller.pullAndProcessProfile(startMicros: 0, extentMicros: 100); + await controller.pullAndProcessProfile( + startMicros: 0, + extentMicros: 100, + processId: 'test', + ); controller.selectCpuStackFrame(testStackFrame); expect( controller.dataNotifier.value, @@ -51,7 +55,11 @@ void main() { expect(controller.processingNotifier.value, false); // [startMicros] and [extentMicros] are arbitrary for testing. - await controller.pullAndProcessProfile(startMicros: 0, extentMicros: 100); + await controller.pullAndProcessProfile( + startMicros: 0, + extentMicros: 100, + processId: 'test', + ); expect( controller.dataNotifier.value, isNot(equals(CpuProfilerController.baseStateCpuProfileData)), @@ -67,7 +75,11 @@ void main() { test('loads filtered data by default', () async { // [startMicros] and [extentMicros] are arbitrary for testing. - await controller.pullAndProcessProfile(startMicros: 0, extentMicros: 100); + await controller.pullAndProcessProfile( + startMicros: 0, + extentMicros: 100, + processId: 'test', + ); final originalData = controller.cpuProfileStore.lookupProfile( label: CpuProfilerController.userTagNone, ); @@ -136,12 +148,15 @@ void main() { } // [startMicros] and [extentMicros] are arbitrary for testing. - await controller.pullAndProcessProfile(startMicros: 0, extentMicros: 100); + await controller.pullAndProcessProfile( + startMicros: 0, + extentMicros: 100, + processId: 'test', + ); expect( controller.dataNotifier.value.stackFrames.values.length, equals(17)); // Match on name. - expect(controller.matchesForSearch(null).length, equals(0)); expect(controller.matchesForSearch('').length, equals(0)); expect(controller.matchesForSearch('render').length, equals(9)); expect(controller.matchesForSearch('RenderObject').length, equals(3)); @@ -166,7 +181,11 @@ void main() { } // [startMicros] and [extentMicros] are arbitrary for testing. - await controller.pullAndProcessProfile(startMicros: 0, extentMicros: 100); + await controller.pullAndProcessProfile( + startMicros: 0, + extentMicros: 100, + processId: 'test', + ); expect( controller.dataNotifier.value.stackFrames.values.length, equals(17)); @@ -193,7 +212,10 @@ void main() { final cpuProfileDataWithTags = CpuProfileData.parse(cpuProfileDataWithUserTagsJson); - await controller.transformer.processData(cpuProfileDataWithTags); + await controller.transformer.processData( + cpuProfileDataWithTags, + processId: 'test', + ); controller.loadProcessedData( cpuProfileDataWithTags, storeAsUserTagNone: true, @@ -263,7 +285,10 @@ void main() { expect(controller.toggleFilters[0].enabled.value, isTrue); final cpuProfileDataWithTags = CpuProfileData.parse(cpuProfileDataWithUserTagsJson); - await controller.transformer.processData(cpuProfileDataWithTags); + await controller.transformer.processData( + cpuProfileDataWithTags, + processId: 'test', + ); controller.loadProcessedData( cpuProfileDataWithTags, storeAsUserTagNone: true, diff --git a/packages/devtools_app/test/cpu_profiler_test.dart b/packages/devtools_app/test/cpu_profiler_test.dart index 78b37586283..798ee0c8f51 100644 --- a/packages/devtools_app/test/cpu_profiler_test.dart +++ b/packages/devtools_app/test/cpu_profiler_test.dart @@ -36,7 +36,10 @@ void main() { final transformer = CpuProfileTransformer(); controller = CpuProfilerController(); cpuProfileData = CpuProfileData.parse(goldenCpuProfileDataJson); - await transformer.processData(cpuProfileData); + await transformer.processData( + cpuProfileData, + processId: 'test', + ); fakeServiceManager = FakeServiceManager(); when(fakeServiceManager.connectedApp.isFlutterNativeAppNow) @@ -50,30 +53,6 @@ void main() { const windowSize = Size(2000.0, 1000.0); final searchFieldKey = GlobalKey(debugLabel: 'test search field key'); - testWidgetsWithWindowSize('builds for null cpuProfileData', windowSize, - (WidgetTester tester) async { - cpuProfiler = CpuProfiler( - data: null, - controller: controller, - searchFieldKey: searchFieldKey, - ); - await tester.pumpWidget(wrap(cpuProfiler)); - expect(find.byType(TabBar), findsOneWidget); - expect(find.byKey(CpuProfiler.dataProcessingKey), findsOneWidget); - expect(find.byType(CpuProfileFlameChart), findsNothing); - expect(find.byType(CpuCallTreeTable), findsNothing); - expect(find.byType(CpuBottomUpTable), findsNothing); - expect(find.byType(UserTagDropdown), findsNothing); - expect(find.byType(ExpandAllButton), findsNothing); - expect(find.byType(CollapseAllButton), findsNothing); - expect(find.byType(FlameChartHelpButton), findsNothing); - expect(find.byKey(searchFieldKey), findsNothing); - expect(find.byKey(CpuProfiler.flameChartTab), findsNothing); - expect(find.byKey(CpuProfiler.callTreeTab), findsNothing); - expect(find.byKey(CpuProfiler.bottomUpTab), findsNothing); - expect(find.byKey(CpuProfiler.summaryTab), findsNothing); - }); - testWidgetsWithWindowSize('builds for empty cpuProfileData', windowSize, (WidgetTester tester) async { cpuProfileData = CpuProfileData.parse(emptyCpuProfileDataJson); @@ -277,8 +256,10 @@ void main() { setUp(() async { controller = ProfilerScreenController(); cpuProfileData = CpuProfileData.parse(cpuProfileDataWithUserTagsJson); - await controller.cpuProfilerController.transformer - .processData(cpuProfileData); + await controller.cpuProfilerController.transformer.processData( + cpuProfileData, + processId: 'test', + ); // Call this to force the value of `_dataByTag[userTagNone]` to be set. controller.cpuProfilerController.loadProcessedData( cpuProfileData, diff --git a/packages/devtools_app/test/test_data/cpu_profile_test_data.dart b/packages/devtools_app/test/test_data/cpu_profile_test_data.dart index 354b616a5cb..108235e9383 100644 --- a/packages/devtools_app/test/test_data/cpu_profile_test_data.dart +++ b/packages/devtools_app/test/test_data/cpu_profile_test_data.dart @@ -2,9 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart=2.9 - -import 'package:devtools_app/devtools_app.dart'; +import 'package:devtools_app/src/primitives/utils.dart'; +import 'package:devtools_app/src/screens/profiler/cpu_profile_model.dart'; final Map goldenCpuProfileDataJson = { 'type': '_CpuProfileTimeline', @@ -654,7 +653,7 @@ final CpuStackFrame stackFrameA = CpuStackFrame( category: 'Dart', rawUrl: '', sourceLine: null, - parentId: null, + parentId: CpuProfileData.rootId, profileMetaData: profileMetaData, )..exclusiveSampleCount = 0; @@ -869,7 +868,7 @@ final CpuStackFrame zeroStackFrame = CpuStackFrame( category: 'Dart', rawUrl: '', sourceLine: null, - parentId: null, + parentId: CpuProfileData.rootId, profileMetaData: zeroProfileMetaData, )..exclusiveSampleCount = 0; @@ -880,6 +879,6 @@ final flutterEngineStackFrame = CpuStackFrame( category: 'Dart', rawUrl: '', sourceLine: null, - parentId: null, + parentId: CpuProfileData.rootId, profileMetaData: profileMetaData, )..exclusiveSampleCount = 1;