mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
![Ryan Macnak](/assets/img/avatar_default.png)
Assume the embedder provides static strings for event labels. TEST=Instruments Bug: https://github.com/dart-lang/sdk/issues/49178 Change-Id: I40aa4996fed2b1230da64d182e6f172f60480fdf Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251145 Reviewed-by: Siva Annamalai <asiva@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
381 lines
12 KiB
C++
381 lines
12 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 "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, 2, 3);
|
|
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]; }
|
|
|
|
intptr_t Size() { return -1; }
|
|
|
|
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);
|
|
}
|
|
|
|
#endif // !PRODUCT
|
|
|
|
} // namespace dart
|