[vm] Add Dart_SetTimelineRecorderCallback.

TEST=ci
Bug: b/245563515
Change-Id: I5f8ada1854be9de5bf08f4492c3166826722dbf3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/258180
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Ryan Macnak 2022-09-09 17:21:44 +00:00 committed by Commit Bot
parent f58e9abf12
commit 79afcf9c7d
5 changed files with 274 additions and 6 deletions

View file

@ -132,7 +132,7 @@ typedef struct {
} Dart_EmbedderInformation;
/**
* Callback provided by the embedder that is used by the vm to request
* Callback provided by the embedder that is used by the VM to request
* information.
*
* \return Returns a pointer to a Dart_EmbedderInformation structure.
@ -152,8 +152,8 @@ typedef void (*Dart_EmbedderInformationCallback)(
* \param callback The callback to invoke.
* \param user_data The user data passed to the callback.
*
* NOTE: If multiple callbacks with the same name are registered, only
* the last callback registered will be remembered.
* NOTE: If multiple callbacks are registered, only the last callback registered
* will be remembered.
*/
DART_EXPORT void Dart_SetEmbedderInformationCallback(
Dart_EmbedderInformationCallback callback);
@ -443,6 +443,82 @@ DART_EXPORT void Dart_TimelineEvent(const char* label,
*/
DART_EXPORT void Dart_SetThreadName(const char* name);
typedef struct {
const char* name;
const char* value;
} Dart_TimelineRecorderEvent_Argument;
#define DART_TIMELINE_RECORDER_CURRENT_VERSION (0x00000001)
typedef struct {
/* Set to DART_TIMELINE_RECORDER_CURRENT_VERSION */
int32_t version;
/* The event's type / phase. */
Dart_Timeline_Event_Type type;
/* The event's timestamp according to the same clock as
* Dart_TimelineGetMicros. For a duration event, this is the beginning time.
*/
int64_t timestamp0;
/* For a duration event, this is the end time. For an async event, this is the
* async id. */
int64_t timestamp1_or_async_id;
/* The current isolate of the event, as if by Dart_GetMainPortId, or
* ILLEGAL_PORT if the event had no current isolate. */
Dart_Port isolate;
/* The current isolate group of the event, as if by
* Dart_CurrentIsolateGroupId, or ILLEGAL_PORT if the event had no current
* isolate group. */
Dart_IsolateGroupId isolate_group;
/* The name / label of the event. */
const char* label;
/* The stream / category of the event. */
const char* stream;
intptr_t argument_count;
Dart_TimelineRecorderEvent_Argument* arguments;
} Dart_TimelineRecorderEvent;
/**
* Callback provided by the embedder to handle the completion of timeline
* events.
*
* \param event A timeline event that has just been completed. The VM keeps
* ownership of the event and any field in it (i.e., the embedder should copy
* any values it needs after the callback returns).
*/
typedef void (*Dart_TimelineRecorderCallback)(
Dart_TimelineRecorderEvent* event);
/**
* Register a `Dart_TimelineRecorderCallback` to be called as timeline events
* are completed.
*
* The callback will be invoked without a current isolate.
*
* The callback will be invoked on the thread completing the event. Because
* `Dart_TimelineEvent` may be called by any thread, the callback may be called
* on any thread.
*
* The callback may be invoked at any time after `Dart_Initialize` is called and
* before `Dart_Cleanup` returns.
*
* If multiple callbacks are registered, only the last callback registered
* will be remembered. Providing a NULL callback will clear the registration
* (i.e., a NULL callback produced a no-op instead of a crash).
*
* Setting a callback is insuffient to receive events through the callback. The
* VM flag `timeline_recorder` must also be set to `callback`.
*/
DART_EXPORT void Dart_SetTimelineRecorderCallback(
Dart_TimelineRecorderCallback callback);
/*
* =======
* Metrics

View file

@ -6424,6 +6424,13 @@ DART_EXPORT void Dart_TimelineEvent(const char* label,
#endif
}
DART_EXPORT void Dart_SetTimelineRecorderCallback(
Dart_TimelineRecorderCallback callback) {
#if defined(SUPPORT_TIMELINE)
Timeline::set_callback(callback);
#endif
}
DART_EXPORT void Dart_SetThreadName(const char* name) {
OSThread* thread = OSThread::Current();
if (thread == NULL) {

View file

@ -47,7 +47,7 @@ DEFINE_FLAG(charp,
timeline_recorder,
"ring",
"Select the timeline recorder used. "
"Valid values: ring, endless, startup, and systrace.")
"Valid values: ring, endless, startup, systrace, file, callback.")
// Implementation notes:
//
@ -142,6 +142,10 @@ static TimelineEventRecorder* CreateTimelineRecorder() {
return new TimelineEventFileRecorder(&flag[5]);
}
if (strcmp("callback", flag) == 0) {
return new TimelineEventEmbedderCallbackRecorder();
}
// Always fall back to the ring recorder.
return new TimelineEventRingRecorder();
}
@ -206,6 +210,7 @@ void Timeline::Init() {
stream_##name##_.set_enabled(HasStream(enabled_streams_, #name));
TIMELINE_STREAM_LIST(TIMELINE_STREAM_FLAG_DEFAULT)
#undef TIMELINE_STREAM_FLAG_DEFAULT
RecorderLock::Init();
}
void Timeline::Cleanup() {
@ -399,6 +404,7 @@ void TimelineEventArguments::Free() {
}
TimelineEventRecorder* Timeline::recorder_ = NULL;
Dart_TimelineRecorderCallback Timeline::callback_ = NULL;
MallocGrowableArray<char*>* Timeline::enabled_streams_ = NULL;
bool Timeline::recorder_discards_clock_values_ = false;
@ -1326,6 +1332,67 @@ void TimelineEventCallbackRecorder::CompleteEvent(TimelineEvent* event) {
delete event;
}
void TimelineEventEmbedderCallbackRecorder::OnEvent(TimelineEvent* event) {
Dart_TimelineRecorderCallback callback = Timeline::callback();
if (callback == NULL) {
return;
}
Dart_TimelineRecorderEvent recorder_event;
recorder_event.version = DART_TIMELINE_RECORDER_CURRENT_VERSION;
switch (event->event_type()) {
case TimelineEvent::kBegin:
recorder_event.type = Dart_Timeline_Event_Begin;
break;
case TimelineEvent::kEnd:
recorder_event.type = Dart_Timeline_Event_End;
break;
case TimelineEvent::kInstant:
recorder_event.type = Dart_Timeline_Event_Instant;
break;
case TimelineEvent::kDuration:
recorder_event.type = Dart_Timeline_Event_Duration;
break;
case TimelineEvent::kAsyncBegin:
recorder_event.type = Dart_Timeline_Event_Async_Begin;
break;
case TimelineEvent::kAsyncEnd:
recorder_event.type = Dart_Timeline_Event_Async_End;
break;
case TimelineEvent::kAsyncInstant:
recorder_event.type = Dart_Timeline_Event_Async_Instant;
break;
case TimelineEvent::kCounter:
recorder_event.type = Dart_Timeline_Event_Counter;
break;
case TimelineEvent::kFlowBegin:
recorder_event.type = Dart_Timeline_Event_Flow_Begin;
break;
case TimelineEvent::kFlowStep:
recorder_event.type = Dart_Timeline_Event_Flow_Step;
break;
case TimelineEvent::kFlowEnd:
recorder_event.type = Dart_Timeline_Event_Flow_End;
break;
default:
// Type not expressible as Dart_Timeline_Event_Type: drop event.
return;
}
recorder_event.timestamp0 = event->timestamp0();
recorder_event.timestamp1_or_async_id = event->timestamp1();
recorder_event.isolate = event->isolate_id();
recorder_event.isolate_group = event->isolate_group_id();
recorder_event.label = event->label();
recorder_event.stream = event->stream()->name();
recorder_event.argument_count = event->GetNumArguments();
recorder_event.arguments =
reinterpret_cast<Dart_TimelineRecorderEvent_Argument*>(
event->arguments());
NoActiveIsolateScope no_active_isolate_scope;
callback(&recorder_event);
}
TimelineEventPlatformRecorder::TimelineEventPlatformRecorder() {}
TimelineEventPlatformRecorder::~TimelineEventPlatformRecorder() {}

View file

@ -130,6 +130,11 @@ class TimelineStream {
class RecorderLock : public AllStatic {
public:
static void Init() {
outstanding_event_writes_.store(0);
shutdown_lock_.store(false, std::memory_order_release);
}
static void EnterLock() {
outstanding_event_writes_.fetch_add(1, std::memory_order_acquire);
}
@ -188,6 +193,11 @@ class Timeline : public AllStatic {
recorder_discards_clock_values_ = value;
}
static Dart_TimelineRecorderCallback callback() { return callback_; }
static void set_callback(Dart_TimelineRecorderCallback callback) {
callback_ = callback;
}
// Reclaim all |TimelineEventBlocks|s that are cached by threads.
static void ReclaimCachedBlocksFromThreads();
@ -218,6 +228,7 @@ class Timeline : public AllStatic {
static void ReclaimCachedBlocksFromThreadsUnsafe();
static TimelineEventRecorder* recorder_;
static Dart_TimelineRecorderCallback callback_;
static MallocGrowableArray<char*>* enabled_streams_;
static bool recorder_discards_clock_values_;
@ -390,6 +401,8 @@ class TimelineEvent {
EventType event_type() const { return EventTypeField::decode(state_); }
TimelineStream* stream() const { return stream_; }
bool IsFinishedDuration() const {
return (event_type() == kDuration) && (timestamp1_ > timestamp0_);
}
@ -409,6 +422,9 @@ class TimelineEvent {
return timestamp1_;
}
int64_t timestamp0() const { return timestamp0_; }
int64_t timestamp1() const { return timestamp1_; }
// The lowest time value stored in this event.
int64_t LowTime() const;
// The highest time value stored in this event.
@ -917,6 +933,9 @@ class TimelineEventCallbackRecorder : public TimelineEventRecorder {
virtual void OnEvent(TimelineEvent* event) = 0;
const char* name() const { return CALLBACK_RECORDER_NAME; }
intptr_t Size() {
return 0;
}
protected:
TimelineEventBlock* GetNewBlockLocked() { return NULL; }
@ -926,6 +945,17 @@ class TimelineEventCallbackRecorder : public TimelineEventRecorder {
void CompleteEvent(TimelineEvent* event);
};
// A recorder that forwards completed events to the callback provided by
// Dart_SetTimelineRecorderCallback.
class TimelineEventEmbedderCallbackRecorder
: public TimelineEventCallbackRecorder {
public:
TimelineEventEmbedderCallbackRecorder() {}
virtual ~TimelineEventEmbedderCallbackRecorder() {}
virtual void OnEvent(TimelineEvent* event);
};
// A recorder that stores events in chains of blocks of events.
// NOTE: This recorder will continue to allocate blocks until it exhausts
// memory.

View file

@ -279,8 +279,6 @@ class EventCounterRecorder : public TimelineEventCallbackRecorder {
intptr_t CountFor(TimelineEvent::EventType type) { return counts_[type]; }
intptr_t Size() { return -1; }
private:
intptr_t counts_[TimelineEvent::kNumEventTypes];
};
@ -377,4 +375,94 @@ TEST_CASE(TimelineRingRecorderJSONOrder) {
#endif // !PRODUCT
#if defined(SUPPORT_TIMELINE)
static Dart_Port expected_isolate;
static Dart_IsolateGroupId expected_isolate_group;
static bool saw_begin;
static bool saw_end;
static void TestTimelineRecorderCallback(Dart_TimelineRecorderEvent* event) {
EXPECT_EQ(DART_TIMELINE_RECORDER_CURRENT_VERSION, event->version);
if ((event->type == Dart_Timeline_Event_Begin) &&
(strcmp(event->label, "TestEvent") == 0)) {
saw_begin = true;
EXPECT_NE(0, event->timestamp0);
EXPECT_EQ(expected_isolate, event->isolate);
EXPECT_EQ(expected_isolate_group, event->isolate_group);
EXPECT_STREQ("Dart", event->stream);
EXPECT_EQ(1, event->argument_count);
EXPECT_STREQ("Dart Arguments", event->arguments[0].name);
EXPECT_STREQ("{\"key\":\"value\"}", event->arguments[0].value);
}
if ((event->type == Dart_Timeline_Event_End) &&
(strcmp(event->label, "TestEvent") == 0)) {
saw_end = true;
EXPECT_NE(0, event->timestamp0);
EXPECT_EQ(expected_isolate, event->isolate);
EXPECT_EQ(expected_isolate_group, event->isolate_group);
EXPECT_STREQ("Dart", event->stream);
EXPECT_EQ(1, event->argument_count);
EXPECT_STREQ("Dart Arguments", event->arguments[0].name);
EXPECT_STREQ("{\"key\":\"value\"}", event->arguments[0].value);
}
}
UNIT_TEST_CASE(DartAPI_SetTimelineRecorderCallback) {
int argc = TesterState::argc + 2;
const char** argv = new const char*[argc];
for (intptr_t i = 0; i < argc - 2; i++) {
argv[i] = TesterState::argv[i];
}
argv[argc - 2] = "--timeline_recorder=callback";
argv[argc - 1] = "--timeline_streams=Dart";
Dart_SetTimelineRecorderCallback(TestTimelineRecorderCallback);
EXPECT(Dart_SetVMFlags(argc, argv) == NULL);
Dart_InitializeParams params;
memset(&params, 0, sizeof(Dart_InitializeParams));
params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION;
params.vm_snapshot_data = TesterState::vm_snapshot_data;
params.create_group = TesterState::create_callback;
params.shutdown_isolate = TesterState::shutdown_callback;
params.cleanup_group = TesterState::group_cleanup_callback;
params.start_kernel_isolate = true;
EXPECT(Dart_Initialize(&params) == NULL);
{
TestIsolateScope scope;
const char* kScriptChars =
"import 'dart:developer';\n"
"main() {\n"
" Timeline.startSync('TestEvent', arguments: {'key':'value'});\n"
" Timeline.finishSync();\n"
"}\n";
Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL);
EXPECT_VALID(lib);
expected_isolate = Dart_GetMainPortId();
EXPECT_NE(ILLEGAL_PORT, expected_isolate);
expected_isolate_group = Dart_CurrentIsolateGroupId();
EXPECT_NE(ILLEGAL_PORT, expected_isolate_group);
saw_begin = false;
saw_end = false;
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL);
EXPECT_VALID(result);
EXPECT(saw_begin);
EXPECT(saw_end);
}
EXPECT(Dart_Cleanup() == NULL);
Dart_SetTimelineRecorderCallback(NULL);
delete[] argv;
}
#endif // defined(SUPPORT_TIMELINE)
} // namespace dart