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(); |
} |