Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ class TimelineEventsController extends PerformanceFeatureController

RateLimiter? _timelinePollingRateLimiter;

/// Whether the connected app uses a timeline recorder, such as systrace,
/// that does not store timeline events in the VM.
bool _vmTimelineUnavailable = false;

@override
Future<void> init() async {
perfettoController.init();
Expand All @@ -155,10 +159,11 @@ class TimelineEventsController extends PerformanceFeatureController

// Load available timeline events.
await forceRefresh();
if (_vmTimelineUnavailable) return;

_timelinePollingRateLimiter = RateLimiter(
_timelinePollingRateLimit,
_pullPerfettoVmTimeline,
_maybePullPerfettoVmTimeline,
);

// Poll for new timeline events.
Expand All @@ -170,6 +175,24 @@ class TimelineEventsController extends PerformanceFeatureController
});
}

/// Pulls the VM timeline, unless the VM has already reported that it does
/// not store timeline events for the connected app's recorder.
///
/// The VM answers with [RPCErrorKind.kInvalidTimelineRequest] for recorders
/// such as systrace, so that error is handled here instead of failing
/// [init]. Any other error is rethrown.
Future<void> _maybePullPerfettoVmTimeline() async {
if (_vmTimelineUnavailable) return;
try {
await _pullPerfettoVmTimeline();
} on RPCError catch (e) {
if (e.code != RPCErrorKind.kInvalidTimelineRequest.code) rethrow;
_vmTimelineUnavailable = true;
_log.info(e.details ?? e.message);
pushVmTimelineUnavailableWarning();
Comment on lines +190 to +192

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[CONCERN] When the VM timeline is determined to be unavailable, we should cancel the periodic polling timer and dispose of the rate limiter. This avoids leaving a useless periodic timer running every 10 seconds in the background.

      _vmTimelineUnavailable = true;
      _pollingTimer?.cancel();
      _pollingTimer = null;
      _timelinePollingRateLimiter?.dispose();
      _timelinePollingRateLimiter = null;
      _log.info(e.details ?? e.message);
      pushVmTimelineUnavailableWarning();

}
}

Future<void> _pullPerfettoVmTimeline({bool isInitialPull = false}) async {
final service = serviceConnection.serviceManager.service;
if (service == null) return;
Expand Down Expand Up @@ -337,7 +360,7 @@ class TimelineEventsController extends PerformanceFeatureController

Future<void> _forceRefresh() async {
debugTraceCallback(() => _log.info('[forceRefresh]'));
await _pullPerfettoVmTimeline();
await _maybePullPerfettoVmTimeline();
processTrackEvents();
await loadPerfettoTrace();
}
Expand Down Expand Up @@ -491,7 +514,7 @@ class TimelineEventsController extends PerformanceFeatureController
}

void _maybePushNoTimelineEventsWarning() {
if (isActiveFeature) {
if (isActiveFeature && !_vmTimelineUnavailable) {
pushNoTimelineEventsAvailableWarning();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,14 @@ void pushNoTimelineEventsAvailableWarning() {
'To avoid this, open the DevTools Performance page earlier.',
);
}

/// Warns that timeline events are unavailable because the connected app uses
/// a timeline recorder that does not store events in the Dart VM.
void pushVmTimelineUnavailableWarning() {
notificationService.push(
'Timeline events are not available because the app uses a timeline '
'recorder, such as systrace, that does not store events in the Dart VM. '
'To see timeline events, restart the app without system tracing enabled.',
isDismissible: true,
);
}
4 changes: 3 additions & 1 deletion packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ TODO: Remove this section if there are not any updates.

## Performance updates

TODO: Remove this section if there are not any updates.
* Fixed the Performance page loading forever when the connected app uses a
timeline recorder that DevTools cannot read from, such as systrace.
[#6524](https://github.com/flutter/devtools/issues/6524)
Comment on lines +27 to +29

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[NIT] According to the DevTools release notes guidelines, the link following the entry should be the PR link (e.g., [#PR_NUMBER](https://github.com/flutter/devtools/pull/PR_NUMBER)) rather than the issue link. Please update this with the PR number once it is available.

References
  1. The release notes guidelines specify that the PR link should follow the sentence (e.g. #1234). (link)


## CPU profiler updates

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:devtools_app_shared/utils.dart';
import 'package:devtools_test/devtools_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:vm_service/vm_service.dart';

import '../../../test_infra/test_data/performance/sample_performance_data.dart';

Expand Down Expand Up @@ -109,5 +110,43 @@ void main() {
expect(eventsController.fullPerfettoTrace, isEmpty);
});
});

test('init completes when the VM timeline is unavailable', () async {
final originalService = fakeServiceManager.serviceManager.service;
addTearDown(
() => fakeServiceManager.serviceManager.service = originalService,
);
final service = MockVmServiceWrapper();
fakeServiceManager.serviceManager.service = service;
when(service.setVMTimelineFlags(any)).thenAnswer((_) async => Success());
when(
service.getVMTimelineMicros(),
).thenAnswer((_) async => Timestamp(timestamp: 0));
// The VM rejects timeline requests while the systrace recorder is used.
when(
service.getPerfettoVMTimeline(
timeOriginMicros: anyNamed('timeOriginMicros'),
timeExtentMicros: anyNamed('timeExtentMicros'),
),
).thenAnswer(
(_) async => throw RPCError(
'getPerfettoVMTimeline',
RPCErrorKind.kInvalidTimelineRequest.code,
),
);
setGlobal(PreferencesController, PreferencesController());
setGlobal(NotificationService, NotificationService());
addTearDown(eventsController.dispose);

await eventsController.init();
await eventsController.forceRefresh();

expect(eventsController.status.value, EventsControllerStatus.ready);
verify(service.getVMTimelineMicros()).called(1);
expect(
notificationService.activeMessages.single.text,
contains('systrace'),
);
});
});
}
Loading