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 6ea11c6d4c4..359f3f31cd8 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 @@ -1257,13 +1257,22 @@ class _CpuProfileTimelineTree { return null; } - String? get resolvedUrl => isCodeTree && _function is vm_service.FuncRef? - ? + String? get resolvedUrl { + if (isCodeTree) { + if (_function is vm_service.FuncRef?) { // TODO(bkonyi): not sure if this is a resolved URL or not, but it's not // critical since this is only displayed when advanced developer mode is // enabled. - (_function as vm_service.FuncRef?)?.location?.script?.uri - : samples.functions?[index].resolvedUrl; + return (_function as vm_service.FuncRef?)?.location?.script?.uri; + } + } else { + final functions = samples.functions; + if (functions == null || index >= functions.length) return null; + return functions[index].resolvedUrl; + } + + return null; + } int? get sourceLine { final function = _function; 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 024de99ef60..d2a835b0269 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,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. +import 'package:flutter/foundation.dart'; import 'package:vm_service/vm_service.dart'; import '../../service/vm_flags.dart' as vm_flags; @@ -28,6 +29,19 @@ extension CpuProfilerExtension on VmService { final cpuSamples = await serviceConnection.serviceManager.service! .getCpuSamples(isolateId, startMicros, extentMicros); + return processCpuSamples( + cpuSamples: cpuSamples, + isolateId: isolateId, + advancedDeveloperModeEnabled: advancedDeveloperModeEnabled, + ); + } + + @visibleForTesting + Future processCpuSamples({ + required CpuSamples cpuSamples, + required String isolateId, + required bool advancedDeveloperModeEnabled, + }) async { // If advanced developer mode is enabled, getCpuSamples will also include // code profile details automatically (e.g., code stacks and a list of code // objects). diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index 460800dd203..f10234dc2b7 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -27,7 +27,8 @@ TODO: Remove this section if there are not any general updates. ## CPU profiler updates -TODO: Remove this section if there are not any general updates. +- Fixed issue preventing CPU profiles from loading when "advanced developer mode" was enabled. - + [#9528](https://github.com/flutter/devtools/pull/9528) ## Memory updates diff --git a/packages/devtools_app/test/screens/cpu_profiler/cpu_profile_model_test.dart b/packages/devtools_app/test/screens/cpu_profiler/cpu_profile_model_test.dart index f759c84e656..dd02b00ef70 100644 --- a/packages/devtools_app/test/screens/cpu_profiler/cpu_profile_model_test.dart +++ b/packages/devtools_app/test/screens/cpu_profiler/cpu_profile_model_test.dart @@ -2,8 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. +import 'dart:convert'; +import 'dart:io'; + import 'package:devtools_app/src/screens/profiler/cpu_profile_model.dart'; +import 'package:devtools_app/src/screens/profiler/cpu_profile_service.dart'; import 'package:devtools_app/src/service/service_manager.dart'; +import 'package:devtools_app/src/shared/globals.dart'; import 'package:devtools_app/src/shared/primitives/utils.dart'; import 'package:devtools_app_shared/utils.dart'; import 'package:devtools_test/devtools_test.dart'; @@ -40,6 +45,27 @@ void main() { expect(filtered.profileMetaData.time!.end, 0); }); + // Regression test for https://github.com/flutter/devtools/issues/9526. + test( + 'code profile loads in advanced developer mode (regression test)', + () async { + final cpuSamplesFile = File( + 'test/test_infra/test_data/cpu_profiler/cpu_samples_with_code_profile.json', + ); + final cpuSamplesJson = jsonDecode(cpuSamplesFile.readAsStringSync()); + final cpuSamples = CpuSamples.parse(cpuSamplesJson)!; + + final profilePair = await serviceConnection.serviceManager.service! + .processCpuSamples( + cpuSamples: cpuSamples, + isolateId: goldenSamplesIsolate, + advancedDeveloperModeEnabled: true, + ); + expect(profilePair.codeProfile, isNotNull); + expect(profilePair.functionProfile, isNotNull); + }, + ); + test('init from parse', () { expect( cpuProfileData.stackFramesJson, diff --git a/packages/devtools_app/test/test_infra/test_data/cpu_profiler/cpu_samples_with_code_profile.json b/packages/devtools_app/test/test_infra/test_data/cpu_profiler/cpu_samples_with_code_profile.json new file mode 100644 index 00000000000..4e2dd05987d --- /dev/null +++ b/packages/devtools_app/test/test_infra/test_data/cpu_profiler/cpu_samples_with_code_profile.json @@ -0,0 +1,107 @@ +{ + "type": "CpuSamples", + "samplePeriod": 50, + "maxStackDepth": 10, + "sampleCount": 1, + "timeOriginMicros": 0, + "timeExtentMicros": 100, + "pid": 12345, + "functions": [ + { + "kind": "Dart", + "inclusiveTicks": 1, + "exclusiveTicks": 0, + "resolvedUrl": "file:///main.dart", + "function": { + "type": "@Function", + "id": "func-0", + "name": "main", + "owner": { "type": "@Library", "id": "lib-0", "name": "", "uri": "file:///main.dart" }, + "isStatic": true, + "isConst": false, + "implicit": false + } + }, + { + "kind": "Dart", + "inclusiveTicks": 1, + "exclusiveTicks": 1, + "resolvedUrl": "file:///main.dart", + "function": { + "type": "@Function", + "id": "func-1", + "name": "helperFunction", + "owner": { "type": "@Library", "id": "lib-0", "name": "", "uri": "file:///main.dart" }, + "isStatic": true, + "isConst": false, + "implicit": false + } + } + ], + "_codes": [ + { + "kind": "Dart", + "inclusiveTicks": 1, + "exclusiveTicks": 0, + "code": { + "type": "@Code", + "kind": "Dart", + "name": "main", + "id": "code-0", + "function": { + "type": "@Function", + "id": "func-0", + "name": "main", + "owner": { "type": "@Library", "id": "lib-0", "name": "", "uri": "file:///main.dart" }, + "isStatic": true, + "isConst": false, + "implicit": false + } + } + }, + { + "kind": "Dart", + "inclusiveTicks": 1, + "exclusiveTicks": 1, + "code": { + "type": "@Code", + "kind": "Dart", + "name": "helperFunction", + "id": "code-1", + "function": { + "type": "@Function", + "id": "func-1", + "name": "helperFunction", + "owner": { "type": "@Library", "id": "lib-0", "name": "", "uri": "file:///main.dart" }, + "isStatic": true, + "isConst": false, + "implicit": false + } + } + }, + { + "kind": "Stub", + "inclusiveTicks": 1, + "exclusiveTicks": 0, + "code": { + "type": "@Code", + "kind": "Stub", + "name": "WriteBarrierStub", + "id": "code-2", + "function": { + "type": "NativeFunction", + "id": "native-2", + "name": "WriteBarrierStub" + } + } + } + ], + "samples": [ + { + "tid": 100, + "timestamp": 50, + "stack": [0, 1], + "_codeStack": [2] + } + ] +}