Skip to content

Commit 22021cc

Browse files
derekxu16Commit Queue
authored andcommitted
[VM/Service] Record timeline events representing completed microtasks
TEST=pkg/vm_service/test/timeline_events_for_completed_microtasks_test CoreLibraryReviewExempt: This CL does not include any core library API changes, it only modifies the implementation of microtasks (by instrumenting them). Change-Id: I54d886db9519c73f9e3218a9cc1c46bc9fe9acc3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/420221 Commit-Queue: Derek Xu <[email protected]> Reviewed-by: Ben Konyi <[email protected]>
1 parent 75311ae commit 22021cc

12 files changed

+354
-18
lines changed

pkg/vm_service/test/common/service_test_common.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
library service_test_common;
66

77
import 'dart:async';
8+
import 'dart:collection' show HashMap;
89
import 'dart:typed_data';
910

1011
import 'package:path/path.dart' as p;
1112
import 'package:test/test.dart';
1213
import 'package:vm_service/vm_service.dart';
14+
import 'package:vm_service_protos/vm_service_protos.dart' show DebugAnnotation;
1315

1416
typedef IsolateTest = Future<void> Function(
1517
VmService service,
@@ -914,3 +916,19 @@ IsolateTest testExpressionEvaluationAndAvailableVariables(
914916
}
915917
};
916918
}
919+
920+
Map<String, String> mapFromListOfDebugAnnotations(
921+
List<DebugAnnotation> debugAnnotations,
922+
) {
923+
return HashMap.fromEntries(
924+
debugAnnotations.map((a) {
925+
if (a.hasStringValue()) {
926+
return MapEntry(a.name, a.stringValue);
927+
} else if (a.hasLegacyJsonValue()) {
928+
return MapEntry(a.name, a.legacyJsonValue);
929+
} else {
930+
throw 'We should not be writing annotations without values';
931+
}
932+
}),
933+
);
934+
}

pkg/vm_service/test/get_perfetto_vm_timeline_rpc_test.dart

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
// VMOptions=
66
// VMOptions=--intern_strings_when_writing_perfetto_timeline
77

8-
import 'dart:collection';
98
import 'dart:convert';
109
import 'dart:developer';
1110
import 'dart:io' show Platform;
@@ -14,6 +13,7 @@ import 'package:test/test.dart';
1413
import 'package:vm_service/vm_service.dart' hide Timeline;
1514
import 'package:vm_service_protos/vm_service_protos.dart';
1615

16+
import 'common/service_test_common.dart' show mapFromListOfDebugAnnotations;
1717
import 'common/test_helper.dart';
1818

1919
void primeTimeline() {
@@ -143,22 +143,6 @@ List<TrackEvent> extractTrackEventsFromTracePackets(
143143
return result;
144144
}
145145

146-
Map<String, String> mapFromListOfDebugAnnotations(
147-
List<DebugAnnotation> debugAnnotations,
148-
) {
149-
return HashMap.fromEntries(
150-
debugAnnotations.map((a) {
151-
if (a.hasStringValue()) {
152-
return MapEntry(a.name, a.stringValue);
153-
} else if (a.hasLegacyJsonValue()) {
154-
return MapEntry(a.name, a.legacyJsonValue);
155-
} else {
156-
throw 'We should not be writing annotations without values';
157-
}
158-
}),
159-
);
160-
}
161-
162146
void checkThatAllEventsHaveIsolateNumbers(Iterable<TrackEvent> events) {
163147
for (final event in events) {
164148
final debugAnnotations =
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:convert';
7+
8+
import 'package:test/test.dart';
9+
import 'package:vm_service/vm_service.dart' hide Timeline;
10+
import 'package:vm_service_protos/vm_service_protos.dart';
11+
12+
import 'common/service_test_common.dart' show mapFromListOfDebugAnnotations;
13+
import 'common/test_helper.dart';
14+
15+
const String shortFile = 'timeline_events_for_completed_microtasks_test.dart';
16+
17+
void primeTimeline() {
18+
for (int i = 0; i < 5; i++) {
19+
scheduleMicrotask(() {});
20+
}
21+
}
22+
23+
Iterable<TrackEvent> extractTrackEventsFromTracePackets(
24+
List<TracePacket> packets,
25+
) =>
26+
packets
27+
.where((packet) => packet.hasTrackEvent())
28+
.map((packet) => packet.trackEvent);
29+
30+
final tests = <IsolateTest>[
31+
(VmService service, IsolateRef isolateRef) async {
32+
final result = await service.getPerfettoVMTimeline();
33+
34+
final trace = Trace.fromBuffer(base64Decode(result.trace!));
35+
final packets = trace.packet;
36+
final mainIsolateMicrotaskEvents =
37+
extractTrackEventsFromTracePackets(packets)
38+
.where((event) => event.name == 'Microtask')
39+
.where((event) {
40+
final debugAnnotations =
41+
mapFromListOfDebugAnnotations(event.debugAnnotations);
42+
return debugAnnotations['isolateId'] == isolateRef.id;
43+
});
44+
expect(mainIsolateMicrotaskEvents.length, greaterThanOrEqualTo(5));
45+
46+
for (final event in mainIsolateMicrotaskEvents) {
47+
final debugAnnotations =
48+
mapFromListOfDebugAnnotations(event.debugAnnotations);
49+
expect(debugAnnotations['microtaskId'], isNotNull);
50+
expect(
51+
debugAnnotations['stack trace captured when microtask was enqueued'],
52+
contains(shortFile),
53+
);
54+
}
55+
},
56+
];
57+
58+
void main([args = const <String>[]]) => runIsolateTests(
59+
args,
60+
tests,
61+
shortFile,
62+
testeeBefore: primeTimeline,
63+
extraArgs: ['--profile-microtasks', '--timeline-streams=Microtask'],
64+
);

runtime/lib/async.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@
55
#include "vm/bootstrap_natives.h"
66
#include "vm/debugger.h"
77
#include "vm/exceptions.h"
8+
#include "vm/flags.h"
9+
#include "vm/microtask_mirror_queues.h"
810
#include "vm/native_entry.h"
911
#include "vm/object_store.h"
1012
#include "vm/runtime_entry.h"
1113

1214
namespace dart {
1315

16+
// This flag is defined in "vm/microtask_mirror_queues.cc".
17+
DECLARE_FLAG(bool, profile_microtasks);
18+
1419
DEFINE_NATIVE_ENTRY(AsyncStarMoveNext_debuggerStepCheck, 0, 1) {
1520
#if !defined(PRODUCT)
1621
GET_NON_NULL_NATIVE_ARGUMENT(Closure, generator, arguments->NativeArgAt(0));
@@ -56,4 +61,41 @@ DEFINE_NATIVE_ENTRY(SuspendState_instantiateClosureWithFutureTypeArgument,
5661
return closure.ptr();
5762
}
5863

64+
DEFINE_NATIVE_ENTRY(MicrotaskMirrorQueue_onScheduleAsyncCallback, 0, 1) {
65+
#if !defined(PRODUCT)
66+
if (FLAG_profile_microtasks) {
67+
GET_NON_NULL_NATIVE_ARGUMENT(StackTrace, stack_trace,
68+
arguments->NativeArgAt(0));
69+
MicrotaskMirrorQueues::GetQueue(static_cast<int64_t>(isolate->main_port()))
70+
->OnScheduleAsyncCallback(stack_trace);
71+
}
72+
#endif // !defined(PRODUCT)
73+
return Object::null();
74+
}
75+
76+
DEFINE_NATIVE_ENTRY(MicrotaskMirrorQueue_onSchedulePriorityAsyncCallback,
77+
0,
78+
0) {
79+
#if !defined(PRODUCT)
80+
if (FLAG_profile_microtasks) {
81+
MicrotaskMirrorQueues::GetQueue(static_cast<int64_t>(isolate->main_port()))
82+
->OnSchedulePriorityAsyncCallback();
83+
}
84+
#endif // !defined(PRODUCT)
85+
return Object::null();
86+
}
87+
88+
DEFINE_NATIVE_ENTRY(MicrotaskMirrorQueue_onAsyncCallbackComplete, 0, 2) {
89+
#if !defined(PRODUCT)
90+
if (FLAG_profile_microtasks) {
91+
GET_NON_NULL_NATIVE_ARGUMENT(Integer, start_time,
92+
arguments->NativeArgAt(0));
93+
GET_NON_NULL_NATIVE_ARGUMENT(Integer, end_time, arguments->NativeArgAt(1));
94+
MicrotaskMirrorQueues::GetQueue(static_cast<int64_t>(isolate->main_port()))
95+
->OnAsyncCallbackComplete(start_time.Value(), end_time.Value());
96+
}
97+
#endif // !defined(PRODUCT)
98+
return Object::null();
99+
}
100+
59101
} // namespace dart

runtime/vm/bootstrap_natives.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ namespace dart {
149149
V(Timeline_getTraceClock, 0) \
150150
V(Timeline_isDartStreamEnabled, 0) \
151151
V(Timeline_reportTaskEvent, 5) \
152+
V(MicrotaskMirrorQueue_onScheduleAsyncCallback, 1) \
153+
V(MicrotaskMirrorQueue_onSchedulePriorityAsyncCallback, 0) \
154+
V(MicrotaskMirrorQueue_onAsyncCallbackComplete, 2) \
152155
V(TypedDataBase_length, 1) \
153156
V(TypedDataBase_setClampedRange, 5) \
154157
V(TypedData_GetFloat32, 2) \

runtime/vm/dart.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "vm/kernel_isolate.h"
3434
#include "vm/message_handler.h"
3535
#include "vm/metrics.h"
36+
#include "vm/microtask_mirror_queues.h"
3637
#include "vm/native_entry.h"
3738
#include "vm/native_message_handler.h"
3839
#include "vm/object.h"
@@ -764,6 +765,7 @@ char* Dart::Cleanup() {
764765
}
765766
Timeline::Cleanup();
766767
#endif
768+
NOT_IN_PRODUCT(MicrotaskMirrorQueues::CleanUp());
767769
Zone::Cleanup();
768770
Random::Cleanup();
769771
// Delete the current thread's TLS and set it's TLS to null.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
#if !defined(PRODUCT)
6+
7+
#include "vm/microtask_mirror_queues.h"
8+
9+
#include <utility>
10+
11+
#include "platform/hashmap.h"
12+
#include "vm/flags.h"
13+
#include "vm/object.h"
14+
#include "vm/timeline.h"
15+
16+
namespace dart {
17+
18+
DEFINE_FLAG(
19+
bool,
20+
profile_microtasks,
21+
false,
22+
"Record information about each microtask. Information about completed "
23+
"microtasks will be written to the \"Microtask\" timeline stream.");
24+
25+
MicrotaskMirrorQueue* MicrotaskMirrorQueues::GetQueue(int64_t isolate_id) {
26+
void* key = reinterpret_cast<void*>(isolate_id);
27+
const intptr_t hash = Utils::WordHash(isolate_id);
28+
29+
MutexLocker ml(&isolate_id_to_queue_lock_);
30+
31+
SimpleHashMap::Entry* entry = isolate_id_to_queue_.Lookup(key, hash, true);
32+
if (entry->value == nullptr) {
33+
entry->value = new MicrotaskMirrorQueue();
34+
}
35+
return static_cast<MicrotaskMirrorQueue*>(entry->value);
36+
}
37+
38+
void MicrotaskMirrorQueue::OnScheduleAsyncCallback(const StackTrace& st) {
39+
if (is_disabled_) {
40+
return;
41+
}
42+
43+
queue_.PushBack(MicrotaskMirrorQueueEntry(
44+
next_available_id_++, CStringUniquePtr(Utils::StrDup(st.ToCString()))));
45+
}
46+
47+
void MicrotaskMirrorQueue::OnSchedulePriorityAsyncCallback() {
48+
// If this function is called, it means that the microtask queue can no longer
49+
// be accurately modeled by a |ListQueue|. This only ever gets called when an
50+
// exception goes unhandled, so we just handle the situation by disabling all
51+
// further reads from / writes to this queue.
52+
is_disabled_ = true;
53+
}
54+
55+
void MicrotaskMirrorQueue::OnAsyncCallbackComplete(int64_t start_time,
56+
int64_t end_time) {
57+
if (is_disabled_) {
58+
return;
59+
}
60+
61+
ASSERT(queue_.Length() >= 1);
62+
MicrotaskMirrorQueueEntry&& front = std::move(queue_.PopFront());
63+
64+
TimelineStream* stream = Timeline::GetMicrotaskStream();
65+
ASSERT(stream != nullptr);
66+
TimelineEvent* event = stream->StartEvent();
67+
if (event != nullptr) {
68+
if (start_time < end_time) {
69+
event->Duration("Microtask", start_time, end_time);
70+
} else {
71+
event->Instant("Microtask", start_time);
72+
}
73+
74+
event->SetNumArguments(2);
75+
event->FormatArgument(0, "microtaskId", "%" Pd, front.id());
76+
event->SetArgument(1, "stack trace captured when microtask was enqueued",
77+
front.ReleaseStackTrace());
78+
event->Complete();
79+
}
80+
}
81+
82+
Mutex MicrotaskMirrorQueues::isolate_id_to_queue_lock_;
83+
84+
SimpleHashMap MicrotaskMirrorQueues::isolate_id_to_queue_(
85+
&SimpleHashMap::SamePointerValue,
86+
MicrotaskMirrorQueues::kIsolateIdToQueueInitialCapacity);
87+
88+
void MicrotaskMirrorQueues::CleanUp() {
89+
for (SimpleHashMap::Entry* entry = isolate_id_to_queue_.Start();
90+
entry != nullptr; entry = isolate_id_to_queue_.Next(entry)) {
91+
MicrotaskMirrorQueue* value =
92+
static_cast<MicrotaskMirrorQueue*>(entry->value);
93+
delete value;
94+
}
95+
}
96+
97+
} // namespace dart
98+
99+
#endif // !defined(PRODUCT)

0 commit comments

Comments
 (0)