Skip to content

Correct units and cumulative bug in GC times#9890

Open
srawlins wants to merge 2 commits into
flutter:masterfrom
srawlins:fix-7080
Open

Correct units and cumulative bug in GC times#9890
srawlins wants to merge 2 commits into
flutter:masterfrom
srawlins:fix-7080

Conversation

@srawlins

Copy link
Copy Markdown
Contributor

Fixes #7080

In the logging page, GC events were displayed with summaries like idle collection in 290 ms. The value 290 was calculated directly from the cumulative times (newSpace.time and oldSpace.time), meaning it represented the total cumulative GC time since isolate startup rather than the duration of this specific GC. In reality, the individual collection took 290 microseconds (which is 0.29 milliseconds), but since it was displaying the cumulative time of 290 ms, it was off by a factor of 1000x.

In this fix, we update _handleGCEvent to track and subtract the previous cumulative GC time from the new cumulative GC time to compute the actual duration delta for this specific GC.

Also format the duration as a millisecond double with 1 decimal place (consistent with how frame times are displayed), and appended it to the log summary (e.g. "idle collection in 100.0 ms").

List which issues are fixed by this PR.

Replace this paragraph with a description of what this PR is changing or adding, and why. If your PR is updating any UI functionality, please include include before/after screenshots and/or a gif of the UI interaction.

Fixes flutter#7080

In the logging page, GC events were displayed with summaries like idle
collection in 290 ms. The value 290 was calculated directly from the cumulative
times (`newSpace.time` and `oldSpace.time`), meaning it represented the total
cumulative GC time since isolate startup rather than the duration of this
specific GC. In reality, the individual collection took 290 microseconds (which
is 0.29 milliseconds), but since it was displaying the cumulative time of 290
ms, it was off by a factor of 1000x.

In this fix, we update `_handleGCEvent` to track and subtract the previous
cumulative GC time from the new cumulative GC time to compute the actual
duration delta for this specific GC.

Also format the duration as a millisecond double with 1 decimal place
(consistent with how frame times are displayed), and appended it to the log
summary (e.g. "idle collection in 100.0 ms").
@srawlins srawlins requested a review from a team as a code owner July 13, 2026 23:10
@srawlins srawlins requested review from bkonyi and removed request for a team July 13, 2026 23:10

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request updates the LoggingController to track and display the duration delta of GC events per isolate, rather than displaying the total cumulative GC time, and adds corresponding tests. The feedback highlights a critical issue where the non-existent Future.syncValue is used in mock services, which will cause compilation errors. Additionally, there are concerns regarding a potential memory leak due to uncleaned isolate GC tracking maps, and a recommendation to add defensive checks against negative GC durations if cumulative times reset.

Comment on lines +131 to +138
Future<UriList> lookupPackageUris(
String isolateId,
List<String> uris,
) => Future.syncValue(UriList(
uris: _resolvedUriMap != null
? (uris.map((e) => _resolvedUriMap[e]).toList())
: null
));

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.

high

[MUST-FIX] Future.syncValue is not a standard Dart Future constructor or method. It should be replaced with Future.value.

Suggested change
Future<UriList> lookupPackageUris(
String isolateId,
List<String> uris,
) => Future.syncValue(UriList(
uris: _resolvedUriMap != null
? (uris.map((e) => _resolvedUriMap[e]).toList())
: null
));
Future<UriList> lookupPackageUris(
String isolateId,
List<String> uris,
) => Future.value(UriList(
uris: _resolvedUriMap != null
? (uris.map((e) => _resolvedUriMap[e]).toList())
: null,
));
References
  1. Categorize Severity: Prefix every comment with a severity like [MUST-FIX] for logical bugs. (link)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

syncValue is being recommended over value. It was introduced in Dart 3.10 or so.

Comment thread packages/devtools_test/lib/src/mocks/fake_vm_service_wrapper.dart
Comment on lines +243 to +248
/// Tracks the previous cumulative GC times (in seconds) for each active isolate.
///
/// This is used to compute the actual duration of the current GC event (by taking
/// the delta between consecutive cumulative times), rather than displaying the
/// total cumulative GC time.
final _previousGcTimesByIsolate = <String, double>{};

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] The _previousGcTimesByIsolate map tracks cumulative GC times for each active isolate, but entries are never removed when an isolate exits. In applications that spawn and destroy many short-lived isolates, this can lead to a memory leak. Consider cleaning up the map when isolates are destroyed (e.g., by listening to isolate exit events via the IsolateManager).

References
  1. Categorize Severity: Prefix every comment with a severity like [CONCERN] for maintainability issues. (link)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GC events are reported with wrong units on logging page

2 participants