dart-sdk/runtime/vm/timeline_test.cc
Ryan Macnak 08634ec4ee [vm] Remove thread time from the VM-internal recorders.
Querying the thread clock takes the overwhelming majority of time when using the VM-internal recorders (ring, startup, endless, file). Including the thread clocks in the timeline allowed visualizing roughly how much an event got scheduled out, but not when. This is a high price to pay for such fuzzy information. Precise scheduling information is available from Fuchsia tracing, systrace or Signposts.

```
import "dart:developer";
main() {
  var n = 1000000;
  var watch = new Stopwatch()..start();
  for (var i = 0; i < n; i++) {
    Timeline.timeSync("EventName", () {});
  }
  print("${watch.elapsedMicroseconds.toDouble() / n} us/event");
}
```

--timeline_recorder=ring      2.751822 -> 0.487936 us/event (5.64x)
--timeline_recorder=systrace  0.440439 -> 0.435997 us/event

TEST=ci
Bug: https://github.com/flutter/flutter/issues/121372
Change-Id: Ib6d625f28384a4e87df2a3305483f0f6facd08ff
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/286661
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
2023-03-03 19:05:28 +00:00

526 lines
17 KiB
C++

// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include <cstring>
#include <memory>
#include "platform/assert.h"
#include "vm/dart_api_impl.h"
#include "vm/dart_api_state.h"
#include "vm/globals.h"
#include "vm/timeline.h"
#include "vm/unit_test.h"
namespace dart {
#ifndef PRODUCT
template <class T>
class TimelineRecorderOverride : public ValueObject {
public:
TimelineRecorderOverride() : recorder_(Timeline::recorder()) {
Timeline::recorder_ = new T();
}
explicit TimelineRecorderOverride(T* recorder)
: recorder_(Timeline::recorder()) {
Timeline::recorder_ = recorder;
}
~TimelineRecorderOverride() {
Timeline::Clear();
delete Timeline::recorder_;
Timeline::recorder_ = recorder_;
}
T* recorder() { return static_cast<T*>(Timeline::recorder()); }
private:
TimelineEventRecorder* recorder_;
};
class TimelineTestHelper : public AllStatic {
public:
static void SetStream(TimelineEvent* event, TimelineStream* stream) {
event->StreamInit(stream);
}
static void FakeThreadEvent(TimelineEventBlock* block,
intptr_t ftid,
const char* label = "fake",
TimelineStream* stream = NULL) {
TimelineEvent* event = block->StartEvent();
ASSERT(event != NULL);
event->DurationBegin(label);
event->thread_ = OSThread::ThreadIdFromIntPtr(ftid);
if (stream != NULL) {
event->StreamInit(stream);
}
}
static void SetBlockThread(TimelineEventBlock* block, intptr_t ftid) {
block->thread_id_ = OSThread::ThreadIdFromIntPtr(ftid);
}
static void FakeDuration(TimelineEventRecorder* recorder,
const char* label,
int64_t start,
int64_t end) {
ASSERT(recorder != NULL);
ASSERT(start < end);
ASSERT(label != NULL);
TimelineEvent* event = recorder->StartEvent();
ASSERT(event != NULL);
event->Duration(label, start, end);
event->Complete();
}
static void FakeBegin(TimelineEventRecorder* recorder,
const char* label,
int64_t start) {
ASSERT(recorder != NULL);
ASSERT(label != NULL);
ASSERT(start >= 0);
TimelineEvent* event = recorder->StartEvent();
ASSERT(event != NULL);
event->Begin(label, start);
event->Complete();
}
static void FakeEnd(TimelineEventRecorder* recorder,
const char* label,
int64_t end) {
ASSERT(recorder != NULL);
ASSERT(label != NULL);
ASSERT(end >= 0);
TimelineEvent* event = recorder->StartEvent();
ASSERT(event != NULL);
event->End(label, end);
event->Complete();
}
static void FinishBlock(TimelineEventBlock* block) { block->Finish(); }
};
TEST_CASE(TimelineEventIsValid) {
// Create a test stream.
TimelineStream stream("testStream", "testStream", false, true);
TimelineEvent event;
TimelineTestHelper::SetStream(&event, &stream);
// Starts invalid.
EXPECT(!event.IsValid());
// Becomes valid.
event.Instant("hello");
EXPECT(event.IsValid());
// Becomes invalid.
event.Reset();
EXPECT(!event.IsValid());
}
TEST_CASE(TimelineEventDuration) {
// Create a test stream.
TimelineStream stream("testStream", "testStream", false, true);
// Create a test event.
TimelineEvent event;
TimelineTestHelper::SetStream(&event, &stream);
event.DurationBegin("apple");
// Measure the duration.
int64_t current_duration = event.TimeDuration();
event.DurationEnd();
// Verify that duration is larger.
EXPECT_GE(event.TimeDuration(), current_duration);
}
TEST_CASE(TimelineEventDurationPrintJSON) {
// Create a test stream.
TimelineStream stream("testStream", "testStream", false, true);
// Create a test event.
TimelineEvent event;
TimelineTestHelper::SetStream(&event, &stream);
event.DurationBegin("apple");
{
// Test printing to JSON.
JSONStream js;
event.PrintJSON(&js);
// Check category
EXPECT_SUBSTRING("\"cat\":\"testStream\"", js.ToCString());
// Check name.
EXPECT_SUBSTRING("\"name\":\"apple\"", js.ToCString());
// Check phase.
EXPECT_SUBSTRING("\"ph\":\"X\"", js.ToCString());
// Check that ts key is present.
EXPECT_SUBSTRING("\"ts\":", js.ToCString());
// Check that dur key is present.
EXPECT_SUBSTRING("\"dur\":", js.ToCString());
}
event.DurationEnd();
}
#if defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_LINUX)
TEST_CASE(TimelineEventPrintSystrace) {
const intptr_t kBufferLength = 1024;
char buffer[kBufferLength];
// Create a test stream.
TimelineStream stream("testStream", "testStream", false, true);
// Create a test event.
TimelineEvent event;
TimelineTestHelper::SetStream(&event, &stream);
// Test a Begin event.
event.Begin("apple", 1, 2);
TimelineEventSystraceRecorder::PrintSystrace(&event, &buffer[0],
kBufferLength);
EXPECT_SUBSTRING("|apple", buffer);
EXPECT_SUBSTRING("B|", buffer);
// Test an End event.
event.End("apple", 2, 3);
TimelineEventSystraceRecorder::PrintSystrace(&event, &buffer[0],
kBufferLength);
EXPECT_STREQ("E", buffer);
// Test a Counter event. We only report the first counter value (in this case
// "4").
event.Counter("CTR", 1);
// We have two counters.
event.SetNumArguments(2);
// Set the first counter value.
event.CopyArgument(0, "cats", "4");
// Set the second counter value.
event.CopyArgument(1, "dogs", "1");
TimelineEventSystraceRecorder::PrintSystrace(&event, &buffer[0],
kBufferLength);
EXPECT_SUBSTRING("C|", buffer);
EXPECT_SUBSTRING("|CTR|4", buffer);
// Test a duration event. This event kind is not supported so we should
// serialize it to an empty string.
event.Duration("DUR", 0, 1);
TimelineEventSystraceRecorder::PrintSystrace(&event, &buffer[0],
kBufferLength);
EXPECT_STREQ("", buffer);
}
#endif // defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_LINUX)
TEST_CASE(TimelineEventArguments) {
// Create a test stream.
TimelineStream stream("testStream", "testStream", false, true);
// Create a test event.
TimelineEvent event;
TimelineTestHelper::SetStream(&event, &stream);
// Allocate room for four arguments.
event.SetNumArguments(4);
// Reset.
event.Reset();
event.DurationBegin("apple");
event.SetNumArguments(2);
event.CopyArgument(0, "arg1", "value1");
event.CopyArgument(1, "arg2", "value2");
event.DurationEnd();
}
TEST_CASE(TimelineEventArgumentsPrintJSON) {
// Create a test stream.
TimelineStream stream("testStream", "testStream", false, true);
// Create a test event.
TimelineEvent event;
TimelineTestHelper::SetStream(&event, &stream);
event.DurationBegin("apple");
event.SetNumArguments(2);
event.CopyArgument(0, "arg1", "value1");
event.CopyArgument(1, "arg2", "value2");
event.DurationEnd();
{
// Test printing to JSON.
JSONStream js;
event.PrintJSON(&js);
// Check both arguments.
EXPECT_SUBSTRING("\"arg1\":\"value1\"", js.ToCString());
EXPECT_SUBSTRING("\"arg2\":\"value2\"", js.ToCString());
}
}
TEST_CASE(TimelineEventBufferPrintJSON) {
JSONStream js;
TimelineEventFilter filter;
Timeline::recorder()->PrintJSON(&js, &filter);
// Check the type.
EXPECT_SUBSTRING("\"type\":\"Timeline\"", js.ToCString());
// Check that there is a traceEvents array.
EXPECT_SUBSTRING("\"traceEvents\":[", js.ToCString());
}
// Count the number of each event type seen.
class EventCounterRecorder : public TimelineEventCallbackRecorder {
public:
EventCounterRecorder() {
for (intptr_t i = 0; i < TimelineEvent::kNumEventTypes; i++) {
counts_[i] = 0;
}
}
void OnEvent(TimelineEvent* event) { counts_[event->event_type()]++; }
intptr_t CountFor(TimelineEvent::EventType type) { return counts_[type]; }
private:
intptr_t counts_[TimelineEvent::kNumEventTypes];
};
TEST_CASE(TimelineEventCallbackRecorderBasic) {
TimelineRecorderOverride<EventCounterRecorder> override;
// Initial counts are all zero.
for (intptr_t i = TimelineEvent::kNone + 1; i < TimelineEvent::kNumEventTypes;
i++) {
EXPECT_EQ(0, override.recorder()->CountFor(
static_cast<TimelineEvent::EventType>(i)));
}
// Create a test stream.
TimelineStream stream("testStream", "testStream", false, true);
TimelineEvent* event = NULL;
event = stream.StartEvent();
EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kDuration));
event->DurationBegin("cabbage");
EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kDuration));
event->DurationEnd();
EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kDuration));
event->Complete();
EXPECT_EQ(1, override.recorder()->CountFor(TimelineEvent::kDuration));
event = stream.StartEvent();
EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kInstant));
event->Instant("instantCabbage");
EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kInstant));
event->Complete();
EXPECT_EQ(1, override.recorder()->CountFor(TimelineEvent::kInstant));
event = stream.StartEvent();
EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kAsyncBegin));
int64_t async_id = thread->GetNextTaskId();
EXPECT(async_id != 0);
event->AsyncBegin("asyncBeginCabbage", async_id);
EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kAsyncBegin));
event->Complete();
EXPECT_EQ(1, override.recorder()->CountFor(TimelineEvent::kAsyncBegin));
event = stream.StartEvent();
EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kAsyncInstant));
event->AsyncInstant("asyncInstantCabbage", async_id);
EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kAsyncInstant));
event->Complete();
EXPECT_EQ(1, override.recorder()->CountFor(TimelineEvent::kAsyncInstant));
event = stream.StartEvent();
EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kAsyncEnd));
event->AsyncEnd("asyncEndCabbage", async_id);
EXPECT_EQ(0, override.recorder()->CountFor(TimelineEvent::kAsyncEnd));
event->Complete();
EXPECT_EQ(1, override.recorder()->CountFor(TimelineEvent::kAsyncEnd));
}
TEST_CASE(TimelineRingRecorderJSONOrder) {
TimelineStream stream("testStream", "testStream", false, true);
TimelineEventRingRecorder* recorder =
new TimelineEventRingRecorder(TimelineEventBlock::kBlockSize * 2);
TimelineRecorderOverride<TimelineEventRingRecorder> override(recorder);
TimelineEventBlock* block_0 = Timeline::recorder()->GetNewBlock();
EXPECT(block_0 != NULL);
TimelineEventBlock* block_1 = Timeline::recorder()->GetNewBlock();
EXPECT(block_1 != NULL);
// Test that we wrapped.
EXPECT(block_0 == Timeline::recorder()->GetNewBlock());
// Emit the earlier event into block_1.
TimelineTestHelper::FakeThreadEvent(block_1, 2, "Alpha", &stream);
OS::Sleep(32);
// Emit the later event into block_0.
TimelineTestHelper::FakeThreadEvent(block_0, 2, "Beta", &stream);
TimelineTestHelper::FinishBlock(block_0);
TimelineTestHelper::FinishBlock(block_1);
JSONStream js;
TimelineEventFilter filter;
Timeline::recorder()->PrintJSON(&js, &filter);
// trace-event has a requirement that events for a thread must have
// monotonically increasing timestamps.
// Verify that "Alpha" comes before "Beta" even though "Beta" is in the first
// block.
const char* alpha = strstr(js.ToCString(), "Alpha");
const char* beta = strstr(js.ToCString(), "Beta");
EXPECT(alpha < beta);
}
// |OSThread::Start()| takes in a function pointer, and only lambdas that don't
// capture can be converted to function pointers. So, we use these macros to
// avoid needing to capture.
#define FAKE_PROCESS_ID 1
#define FAKE_TRACE_ID 1
TEST_CASE(TimelineTrackMetadataRace) {
struct ReportMetadataArguments {
TimelineEventRingRecorder* recorder;
ThreadJoinId join_id = OSThread::kInvalidThreadJoinId;
};
TimelineEventRingRecorder recorder;
// Try concurrently reading from / writing to the metadata map. I don't think
// it's possible to assert anything about the outcome, because of scheduling
// uncertainty. This test is just used to ensure that TSAN checks the metadata
// map code.
JSONStream js;
TimelineEventFilter filter;
const ReportMetadataArguments report_metadata_1_arguments{&recorder};
const ReportMetadataArguments report_metadata_2_arguments{&recorder};
OSThread::Start(
"ReportMetadata1",
[](uword arguments_ptr) {
ReportMetadataArguments& arguments =
*reinterpret_cast<ReportMetadataArguments*>(arguments_ptr);
arguments.recorder->AddTrackMetadataBasedOnThread(
FAKE_PROCESS_ID, FAKE_TRACE_ID, "Thread 1");
arguments.join_id =
OSThread::GetCurrentThreadJoinId(OSThread::Current());
},
reinterpret_cast<uword>(&report_metadata_1_arguments));
OSThread::Start(
"ReportMetadata2",
[](uword arguments_ptr) {
ReportMetadataArguments& arguments =
*reinterpret_cast<ReportMetadataArguments*>(arguments_ptr);
arguments.recorder->AddTrackMetadataBasedOnThread(
FAKE_PROCESS_ID, FAKE_TRACE_ID, "Incorrect Name");
arguments.join_id =
OSThread::GetCurrentThreadJoinId(OSThread::Current());
},
reinterpret_cast<uword>(&report_metadata_2_arguments));
recorder.PrintJSON(&js, &filter);
while (
report_metadata_1_arguments.join_id == OSThread::kInvalidThreadJoinId ||
report_metadata_2_arguments.join_id == OSThread::kInvalidThreadJoinId) {
// Spin until the join IDs have been set.
}
OSThread::Join(report_metadata_1_arguments.join_id);
OSThread::Join(report_metadata_2_arguments.join_id);
}
#undef FAKE_PROCESS_ID
#undef FAKE_TRACE_ID
#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