| Index: runtime/vm/timeline.cc
|
| diff --git a/runtime/vm/timeline.cc b/runtime/vm/timeline.cc
|
| index 833d1f8b6199403df773689eb24ca4412abd70b5..ecdd085124b9e29788d0b591c5ed7b623dd22022 100644
|
| --- a/runtime/vm/timeline.cc
|
| +++ b/runtime/vm/timeline.cc
|
| @@ -4,6 +4,7 @@
|
|
|
| #include <cstdlib>
|
|
|
| +#include "vm/atomic.h"
|
| #include "vm/isolate.h"
|
| #include "vm/json_stream.h"
|
| #include "vm/lockers.h"
|
| @@ -16,6 +17,58 @@ namespace dart {
|
| DEFINE_FLAG(bool, trace_timeline, false, "Trace timeline backend");
|
| DEFINE_FLAG(bool, complete_timeline, false, "Record the complete timeline");
|
|
|
| +DEFINE_FLAG(charp, timeline_dir, NULL,
|
| + "Enable all timeline trace streams and output VM global trace "
|
| + "into specified directory.");
|
| +
|
| +void Timeline::InitOnce() {
|
| + ASSERT(recorder_ == NULL);
|
| + // Default to ring recorder being enabled.
|
| + const bool use_ring_recorder = true;
|
| + // Some flags require that we use the endless recorder.
|
| + const bool use_endless_recorder = (FLAG_timeline_dir != NULL);
|
| + if (use_endless_recorder) {
|
| + recorder_ = new TimelineEventEndlessRecorder();
|
| + } else if (use_ring_recorder) {
|
| + recorder_ = new TimelineEventRingRecorder();
|
| + }
|
| + vm_stream_ = new TimelineStream();
|
| + vm_stream_->Init("VM", EnableStreamByDefault("VM"));
|
| +}
|
| +
|
| +
|
| +void Timeline::Shutdown() {
|
| + ASSERT(recorder_ != NULL);
|
| + if (FLAG_timeline_dir != NULL) {
|
| + recorder_->WriteTo(FLAG_timeline_dir);
|
| + }
|
| + delete recorder_;
|
| + recorder_ = NULL;
|
| + delete vm_stream_;
|
| + vm_stream_ = NULL;
|
| +}
|
| +
|
| +
|
| +TimelineEventRecorder* Timeline::recorder() {
|
| + return recorder_;
|
| +}
|
| +
|
| +
|
| +bool Timeline::EnableStreamByDefault(const char* stream_name) {
|
| + // TODO(johnmccutchan): Allow for command line control over streams.
|
| + return FLAG_timeline_dir != NULL;
|
| +}
|
| +
|
| +
|
| +TimelineStream* Timeline::GetVMStream() {
|
| + ASSERT(vm_stream_ != NULL);
|
| + return vm_stream_;
|
| +}
|
| +
|
| +
|
| +TimelineEventRecorder* Timeline::recorder_ = NULL;
|
| +TimelineStream* Timeline::vm_stream_ = NULL;
|
| +
|
| TimelineEvent::TimelineEvent()
|
| : timestamp0_(0),
|
| timestamp1_(0),
|
| @@ -23,7 +76,7 @@ TimelineEvent::TimelineEvent()
|
| arguments_length_(0),
|
| state_(0),
|
| label_(NULL),
|
| - stream_(NULL),
|
| + category_(""),
|
| thread_(OSThread::kInvalidThreadId) {
|
| }
|
|
|
| @@ -36,20 +89,18 @@ TimelineEvent::~TimelineEvent() {
|
| void TimelineEvent::Reset() {
|
| set_event_type(kNone);
|
| thread_ = OSThread::kInvalidThreadId;
|
| - stream_ = NULL;
|
| + isolate_ = NULL;
|
| + category_ = "";
|
| label_ = NULL;
|
| FreeArguments();
|
| }
|
|
|
|
|
| -int64_t TimelineEvent::AsyncBegin(const char* label) {
|
| +void TimelineEvent::AsyncBegin(const char* label, int64_t async_id) {
|
| Init(kAsyncBegin, label);
|
| timestamp0_ = OS::GetCurrentTimeMicros();
|
| - ASSERT(stream_ != NULL);
|
| - int64_t async_id = stream_->GetNextSeq();
|
| // Overload timestamp1_ with the async_id.
|
| timestamp1_ = async_id;
|
| - return async_id;
|
| }
|
|
|
|
|
| @@ -142,7 +193,10 @@ void TimelineEvent::CopyArgument(intptr_t i,
|
|
|
|
|
| void TimelineEvent::Complete() {
|
| - stream_->CompleteEvent(this);
|
| + TimelineEventRecorder* recorder = Timeline::recorder();
|
| + if (recorder != NULL) {
|
| + recorder->CompleteEvent(this);
|
| + }
|
| }
|
|
|
|
|
| @@ -160,8 +214,11 @@ void TimelineEvent::FreeArguments() {
|
|
|
|
|
| void TimelineEvent::StreamInit(TimelineStream* stream) {
|
| - ASSERT(stream != NULL);
|
| - stream_ = stream;
|
| + if (stream != NULL) {
|
| + category_ = stream->name();
|
| + } else {
|
| + category_ = "";
|
| + }
|
| }
|
|
|
|
|
| @@ -172,6 +229,7 @@ void TimelineEvent::Init(EventType event_type,
|
| timestamp0_ = 0;
|
| timestamp1_ = 0;
|
| thread_ = OSThread::GetCurrentThreadId();
|
| + isolate_ = Isolate::Current();
|
| label_ = label;
|
| FreeArguments();
|
| }
|
| @@ -182,7 +240,7 @@ void TimelineEvent::PrintJSON(JSONStream* stream) const {
|
| int64_t pid = OS::ProcessId();
|
| int64_t tid = OSThread::ThreadIdToIntPtr(thread_);
|
| obj.AddProperty("name", label_);
|
| - obj.AddProperty("cat", stream_->name());
|
| + obj.AddProperty("cat", category_);
|
| obj.AddProperty64("tid", tid);
|
| obj.AddProperty64("pid", pid);
|
| obj.AddPropertyTimeMillis("ts", TimeOrigin());
|
| @@ -246,10 +304,8 @@ int64_t TimelineEvent::TimeDuration() const {
|
|
|
|
|
| TimelineStream::TimelineStream()
|
| - : recorder_(NULL),
|
| - name_(NULL),
|
| - enabled_(false),
|
| - seq_(0) {
|
| + : name_(NULL),
|
| + enabled_(false) {
|
| }
|
|
|
|
|
| @@ -260,11 +316,12 @@ void TimelineStream::Init(const char* name, bool enabled) {
|
|
|
|
|
| TimelineEvent* TimelineStream::StartEvent() {
|
| - if (!enabled_ || (recorder_ == NULL)) {
|
| + TimelineEventRecorder* recorder = Timeline::recorder();
|
| + if (!enabled_ || (recorder == NULL)) {
|
| return NULL;
|
| }
|
| ASSERT(name_ != NULL);
|
| - TimelineEvent* event = recorder_->StartEvent();
|
| + TimelineEvent* event = recorder->StartEvent();
|
| if (event != NULL) {
|
| event->StreamInit(this);
|
| }
|
| @@ -272,23 +329,6 @@ TimelineEvent* TimelineStream::StartEvent() {
|
| }
|
|
|
|
|
| -void TimelineStream::CompleteEvent(TimelineEvent* event) {
|
| - if (!enabled_ || (recorder_ == NULL)) {
|
| - return;
|
| - }
|
| - recorder_->CompleteEvent(event);
|
| -}
|
| -
|
| -
|
| -int64_t TimelineStream::GetNextSeq() {
|
| - seq_++;
|
| - if (seq_ < 0) {
|
| - seq_ = 0;
|
| - }
|
| - return seq_;
|
| -}
|
| -
|
| -
|
| void TimelineDurationScope::FormatArgument(intptr_t i,
|
| const char* name,
|
| const char* fmt, ...) {
|
| @@ -310,7 +350,22 @@ void TimelineDurationScope::FormatArgument(intptr_t i,
|
| }
|
|
|
|
|
| -TimelineEventRecorder::TimelineEventRecorder() {
|
| +TimelineEventFilter::TimelineEventFilter() {
|
| +}
|
| +
|
| +
|
| +TimelineEventFilter::~TimelineEventFilter() {
|
| +}
|
| +
|
| +
|
| +IsolateTimelineEventFilter::IsolateTimelineEventFilter(Isolate* isolate)
|
| + : isolate_(isolate) {
|
| +}
|
| +
|
| +
|
| +TimelineEventRecorder::TimelineEventRecorder()
|
| + : global_block_(NULL),
|
| + async_id_(0) {
|
| }
|
|
|
|
|
| @@ -321,25 +376,58 @@ void TimelineEventRecorder::PrintJSONMeta(JSONArray* events) const {
|
| TimelineEvent* TimelineEventRecorder::ThreadBlockStartEvent() {
|
| // Grab the thread's timeline event block.
|
| Thread* thread = Thread::Current();
|
| + ASSERT(thread != NULL);
|
| +
|
| + if (thread->isolate() == NULL) {
|
| + // Non-isolate thread case. This should be infrequent.
|
| + return GlobalBlockStartEvent();
|
| + }
|
| +
|
| TimelineEventBlock* thread_block = thread->timeline_block();
|
| - if ((thread_block == NULL) || thread_block->IsFull()) {
|
| - // If it is full, request a new block.
|
| - thread_block = GetNewBlock();
|
| +
|
| + if ((thread_block != NULL) && thread_block->IsFull()) {
|
| + MutexLocker ml(&lock_);
|
| + // Thread has a block and it is full:
|
| + // 1) Mark it as finished.
|
| + thread_block->Finish();
|
| + // 2) Allocate a new block.
|
| + thread_block = GetNewBlockLocked(thread->isolate());
|
| + thread->set_timeline_block(thread_block);
|
| + } else if (thread_block == NULL) {
|
| + MutexLocker ml(&lock_);
|
| + // Thread has no block. Attempt to allocate one.
|
| + thread_block = GetNewBlockLocked(thread->isolate());
|
| thread->set_timeline_block(thread_block);
|
| }
|
| - if (thread_block == NULL) {
|
| - // Could not allocate block.
|
| - return NULL;
|
| + if (thread_block != NULL) {
|
| + ASSERT(!thread_block->IsFull());
|
| + return thread_block->StartEvent();
|
| }
|
| - ASSERT(thread_block != NULL);
|
| - ASSERT(!thread_block->IsFull());
|
| - return thread_block->StartEvent();
|
| + return NULL;
|
| }
|
|
|
|
|
| -void TimelineEventRecorder::WriteTo(const char* directory) {
|
| - Isolate* isolate = Isolate::Current();
|
|
|
| +TimelineEvent* TimelineEventRecorder::GlobalBlockStartEvent() {
|
| + MutexLocker ml(&lock_);
|
| + if ((global_block_ != NULL) && global_block_->IsFull()) {
|
| + // Global block is full.
|
| + global_block_->Finish();
|
| + global_block_ = NULL;
|
| + }
|
| + if (global_block_ == NULL) {
|
| + // Allocate a new block.
|
| + global_block_ = GetNewBlockLocked(NULL);
|
| + }
|
| + if (global_block_ != NULL) {
|
| + ASSERT(!global_block_->IsFull());
|
| + return global_block_->StartEvent();
|
| + }
|
| + return NULL;
|
| +}
|
| +
|
| +
|
| +void TimelineEventRecorder::WriteTo(const char* directory) {
|
| Dart_FileOpenCallback file_open = Isolate::file_open_callback();
|
| Dart_FileWriteCallback file_write = Isolate::file_write_callback();
|
| Dart_FileCloseCallback file_close = Isolate::file_close_callback();
|
| @@ -347,30 +435,54 @@ void TimelineEventRecorder::WriteTo(const char* directory) {
|
| return;
|
| }
|
|
|
| + FinishGlobalBlock();
|
| +
|
| JSONStream js;
|
| - PrintJSON(&js);
|
| + TimelineEventFilter filter;
|
| + PrintJSON(&js, &filter);
|
|
|
| - const char* format = "%s/dart-timeline-%" Pd "-%" Pd ".json";
|
| + const char* format = "%s/dart-timeline-%" Pd ".json";
|
| intptr_t pid = OS::ProcessId();
|
| - intptr_t len = OS::SNPrint(NULL, 0, format,
|
| - directory, pid, isolate->main_port());
|
| - char* filename = Thread::Current()->zone()->Alloc<char>(len + 1);
|
| - OS::SNPrint(filename, len + 1, format,
|
| - directory, pid, isolate->main_port());
|
| + intptr_t len = OS::SNPrint(NULL, 0, format, directory, pid);
|
| + char* filename = reinterpret_cast<char*>(malloc(len + 1));
|
| + OS::SNPrint(filename, len + 1, format, directory, pid);
|
| void* file = (*file_open)(filename, true);
|
| if (file == NULL) {
|
| OS::Print("Failed to write timeline file: %s\n", filename);
|
| + free(filename);
|
| return;
|
| }
|
| + free(filename);
|
| (*file_write)(js.buffer()->buf(), js.buffer()->length(), file);
|
| (*file_close)(file);
|
| }
|
|
|
|
|
| +void TimelineEventRecorder::FinishGlobalBlock() {
|
| + MutexLocker ml(&lock_);
|
| + if (global_block_ != NULL) {
|
| + global_block_->Finish();
|
| + global_block_ = NULL;
|
| + }
|
| +}
|
| +
|
| +
|
| +int64_t TimelineEventRecorder::GetNextAsyncId() {
|
| + // TODO(johnmccutchan): Gracefully handle wrap around.
|
| + uint32_t next = static_cast<uint32_t>(
|
| + AtomicOperations::FetchAndIncrement(&async_id_));
|
| + return static_cast<int64_t>(next);
|
| +}
|
| +
|
| +
|
| +TimelineEventBlock* TimelineEventRecorder::GetNewBlock() {
|
| + MutexLocker ml(&lock_);
|
| + return GetNewBlockLocked(Isolate::Current());
|
| +}
|
| +
|
|
|
| TimelineEventRingRecorder::TimelineEventRingRecorder(intptr_t capacity)
|
| : blocks_(NULL),
|
| - event_objects_(Array::null()),
|
| capacity_(capacity),
|
| num_blocks_(0),
|
| block_cursor_(0) {
|
| @@ -389,8 +501,6 @@ TimelineEventRingRecorder::TimelineEventRingRecorder(intptr_t capacity)
|
| for (intptr_t i = 0; i < num_blocks_ - 1; i++) {
|
| blocks_[i]->set_next(blocks_[i + 1]);
|
| }
|
| - const Array& array = Array::Handle(Array::New(capacity, Heap::kOld));
|
| - event_objects_ = array.raw();
|
| }
|
|
|
|
|
| @@ -401,11 +511,12 @@ TimelineEventRingRecorder::~TimelineEventRingRecorder() {
|
| delete block;
|
| }
|
| free(blocks_);
|
| - event_objects_ = Array::null();
|
| }
|
|
|
|
|
| -void TimelineEventRingRecorder::PrintJSONEvents(JSONArray* events) const {
|
| +void TimelineEventRingRecorder::PrintJSONEvents(
|
| + JSONArray* events,
|
| + TimelineEventFilter* filter) const {
|
| intptr_t block_offset = FindOldestBlockIndex();
|
| if (block_offset == -1) {
|
| // All blocks are empty.
|
| @@ -414,13 +525,12 @@ void TimelineEventRingRecorder::PrintJSONEvents(JSONArray* events) const {
|
| for (intptr_t block_idx = 0; block_idx < num_blocks_; block_idx++) {
|
| TimelineEventBlock* block =
|
| blocks_[(block_idx + block_offset) % num_blocks_];
|
| - if (block->IsEmpty()) {
|
| - // Skip empty blocks.
|
| + if (!filter->IncludeBlock(block)) {
|
| continue;
|
| }
|
| for (intptr_t event_idx = 0; event_idx < block->length(); event_idx++) {
|
| TimelineEvent* event = block->At(event_idx);
|
| - if (event->IsValid()) {
|
| + if (filter->IncludeEvent(event)) {
|
| events->AddValue(event);
|
| }
|
| }
|
| @@ -428,35 +538,34 @@ void TimelineEventRingRecorder::PrintJSONEvents(JSONArray* events) const {
|
| }
|
|
|
|
|
| -void TimelineEventRingRecorder::PrintJSON(JSONStream* js) {
|
| +void TimelineEventRingRecorder::PrintJSON(JSONStream* js,
|
| + TimelineEventFilter* filter) {
|
| MutexLocker ml(&lock_);
|
| JSONObject topLevel(js);
|
| topLevel.AddProperty("type", "_Timeline");
|
| {
|
| JSONArray events(&topLevel, "traceEvents");
|
| PrintJSONMeta(&events);
|
| - PrintJSONEvents(&events);
|
| + PrintJSONEvents(&events, filter);
|
| }
|
| }
|
|
|
|
|
| -TimelineEventBlock* TimelineEventRingRecorder::GetNewBlock() {
|
| - MutexLocker ml(&lock_);
|
| - return GetNewBlockLocked();
|
| -}
|
| -
|
| -
|
| -TimelineEventBlock* TimelineEventRingRecorder::GetHeadBlock() {
|
| +TimelineEventBlock* TimelineEventRingRecorder::GetHeadBlockLocked() {
|
| return blocks_[0];
|
| }
|
|
|
|
|
| -TimelineEventBlock* TimelineEventRingRecorder::GetNewBlockLocked() {
|
| +TimelineEventBlock* TimelineEventRingRecorder::GetNewBlockLocked(
|
| + Isolate* isolate) {
|
| + // TODO(johnmccutchan): This function should only hand out blocks
|
| + // which have been marked as finished.
|
| if (block_cursor_ == num_blocks_) {
|
| block_cursor_ = 0;
|
| }
|
| TimelineEventBlock* block = blocks_[block_cursor_++];
|
| block->Reset();
|
| + block->Open(isolate);
|
| return block;
|
| }
|
|
|
| @@ -497,7 +606,8 @@ TimelineEventStreamingRecorder::~TimelineEventStreamingRecorder() {
|
| }
|
|
|
|
|
| -void TimelineEventStreamingRecorder::PrintJSON(JSONStream* js) {
|
| +void TimelineEventStreamingRecorder::PrintJSON(JSONStream* js,
|
| + TimelineEventFilter* filter) {
|
| JSONObject topLevel(js);
|
| topLevel.AddProperty("type", "_Timeline");
|
| {
|
| @@ -526,25 +636,20 @@ TimelineEventEndlessRecorder::TimelineEventEndlessRecorder()
|
| }
|
|
|
|
|
| -void TimelineEventEndlessRecorder::PrintJSON(JSONStream* js) {
|
| +void TimelineEventEndlessRecorder::PrintJSON(JSONStream* js,
|
| + TimelineEventFilter* filter) {
|
| MutexLocker ml(&lock_);
|
| JSONObject topLevel(js);
|
| topLevel.AddProperty("type", "_Timeline");
|
| {
|
| JSONArray events(&topLevel, "traceEvents");
|
| PrintJSONMeta(&events);
|
| - PrintJSONEvents(&events);
|
| + PrintJSONEvents(&events, filter);
|
| }
|
| }
|
|
|
|
|
| -TimelineEventBlock* TimelineEventEndlessRecorder::GetNewBlock() {
|
| - MutexLocker ml(&lock_);
|
| - return GetNewBlockLocked();
|
| -}
|
| -
|
| -
|
| -TimelineEventBlock* TimelineEventEndlessRecorder::GetHeadBlock() {
|
| +TimelineEventBlock* TimelineEventEndlessRecorder::GetHeadBlockLocked() {
|
| return head_;
|
| }
|
|
|
| @@ -559,21 +664,30 @@ void TimelineEventEndlessRecorder::CompleteEvent(TimelineEvent* event) {
|
| }
|
|
|
|
|
| -TimelineEventBlock* TimelineEventEndlessRecorder::GetNewBlockLocked() {
|
| +TimelineEventBlock* TimelineEventEndlessRecorder::GetNewBlockLocked(
|
| + Isolate* isolate) {
|
| TimelineEventBlock* block = new TimelineEventBlock(block_index_++);
|
| block->set_next(head_);
|
| + block->Open(isolate);
|
| head_ = block;
|
| return head_;
|
| }
|
|
|
|
|
| -void TimelineEventEndlessRecorder::PrintJSONEvents(JSONArray* events) const {
|
| +void TimelineEventEndlessRecorder::PrintJSONEvents(
|
| + JSONArray* events,
|
| + TimelineEventFilter* filter) const {
|
| TimelineEventBlock* current = head_;
|
| +
|
| while (current != NULL) {
|
| + if (!filter->IncludeBlock(current)) {
|
| + current = current->next();
|
| + continue;
|
| + }
|
| intptr_t length = current->length();
|
| for (intptr_t i = 0; i < length; i++) {
|
| TimelineEvent* event = current->At(i);
|
| - if (!event->IsValid()) {
|
| + if (!filter->IncludeEvent(event)) {
|
| continue;
|
| }
|
| events->AddValue(event);
|
| @@ -659,6 +773,19 @@ void TimelineEventBlock::Reset() {
|
| events_[i].Reset();
|
| }
|
| length_ = 0;
|
| + isolate_ = NULL;
|
| + open_ = false;
|
| +}
|
| +
|
| +
|
| +void TimelineEventBlock::Open(Isolate* isolate) {
|
| + isolate_ = isolate;
|
| + open_ = true;
|
| +}
|
| +
|
| +
|
| +void TimelineEventBlock::Finish() {
|
| + open_ = false;
|
| }
|
|
|
|
|
| @@ -689,7 +816,7 @@ void TimelineEventBlockIterator::Reset(TimelineEventRecorder* recorder) {
|
| // Lock new recorder.
|
| recorder_->lock_.Lock();
|
| // Queue up first block.
|
| - current_ = recorder_->GetHeadBlock();
|
| + current_ = recorder_->GetHeadBlockLocked();
|
| }
|
|
|
|
|
|
|