| // 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 <cstdlib> |
| |
| #include "vm/isolate.h" |
| #include "vm/json_stream.h" |
| #include "vm/object.h" |
| #include "vm/thread.h" |
| #include "vm/timeline.h" |
| |
| namespace dart { |
| |
| DEFINE_FLAG(bool, trace_timeline, false, "Timeline trace"); |
| |
| TimelineEvent::TimelineEvent() |
| : timestamp0_(0), |
| timestamp1_(0), |
| arguments_(NULL), |
| arguments_length_(0), |
| state_(0), |
| label_(NULL), |
| stream_(NULL), |
| thread_(NULL) { |
| } |
| |
| |
| TimelineEvent::~TimelineEvent() { |
| Reset(); |
| } |
| |
| |
| void TimelineEvent::Reset() { |
| set_event_type(kNone); |
| thread_ = NULL; |
| stream_ = NULL; |
| label_ = NULL; |
| FreeArguments(); |
| } |
| |
| |
| int64_t TimelineEvent::AsyncBegin(const char* label) { |
| 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; |
| } |
| |
| |
| void TimelineEvent::AsyncInstant(const char* label, |
| int64_t async_id) { |
| Init(kAsyncInstant, label); |
| timestamp0_ = OS::GetCurrentTimeMicros(); |
| // Overload timestamp1_ with the async_id. |
| timestamp1_ = async_id; |
| } |
| |
| |
| void TimelineEvent::AsyncEnd(const char* label, |
| int64_t async_id) { |
| Init(kAsyncEnd, label); |
| timestamp0_ = OS::GetCurrentTimeMicros(); |
| // Overload timestamp1_ with the async_id. |
| timestamp1_ = async_id; |
| } |
| |
| |
| void TimelineEvent::DurationBegin(const char* label) { |
| Init(kDuration, label); |
| timestamp0_ = OS::GetCurrentTimeMicros(); |
| } |
| |
| |
| void TimelineEvent::DurationEnd() { |
| timestamp1_ = OS::GetCurrentTimeMicros(); |
| } |
| |
| |
| void TimelineEvent::Instant(const char* label) { |
| Init(kInstant, label); |
| timestamp0_ = OS::GetCurrentTimeMicros(); |
| } |
| |
| |
| void TimelineEvent::Duration(const char* label, |
| int64_t start_micros, |
| int64_t end_micros) { |
| Init(kDuration, label); |
| timestamp0_ = start_micros; |
| timestamp1_ = end_micros; |
| } |
| |
| |
| void TimelineEvent::SetNumArguments(intptr_t length) { |
| // Cannot call this twice. |
| ASSERT(arguments_ == NULL); |
| ASSERT(arguments_length_ == 0); |
| arguments_length_ = length; |
| arguments_ = reinterpret_cast<TimelineEventArgument*>( |
| calloc(sizeof(TimelineEventArgument), length)); |
| } |
| |
| |
| void TimelineEvent::SetArgument(intptr_t i, const char* name, char* argument) { |
| ASSERT(i >= 0); |
| ASSERT(i < arguments_length_); |
| arguments_[i].name = name; |
| arguments_[i].value = argument; |
| } |
| |
| |
| void TimelineEvent::FormatArgument(intptr_t i, const char* name, |
| const char* fmt, ...) { |
| ASSERT(i >= 0); |
| ASSERT(i < arguments_length_); |
| va_list args; |
| va_start(args, fmt); |
| intptr_t len = OS::VSNPrint(NULL, 0, fmt, args); |
| va_end(args); |
| |
| char* buffer = reinterpret_cast<char*>(malloc(len + 1)); |
| va_list args2; |
| va_start(args2, fmt); |
| OS::VSNPrint(buffer, (len + 1), fmt, args2); |
| va_end(args2); |
| |
| SetArgument(i, name, buffer); |
| } |
| |
| |
| void TimelineEvent::CopyArgument(intptr_t i, |
| const char* name, |
| const char* argument) { |
| SetArgument(i, name, strdup(argument)); |
| } |
| |
| |
| void TimelineEvent::Complete() { |
| stream_->CompleteEvent(this); |
| } |
| |
| |
| void TimelineEvent::FreeArguments() { |
| if (arguments_ == NULL) { |
| return; |
| } |
| for (intptr_t i = 0; i < arguments_length_; i++) { |
| free(arguments_[i].value); |
| } |
| free(arguments_); |
| arguments_ = NULL; |
| arguments_length_ = 0; |
| } |
| |
| |
| void TimelineEvent::StreamInit(TimelineStream* stream) { |
| ASSERT(stream != NULL); |
| stream_ = stream; |
| } |
| |
| |
| void TimelineEvent::Init(EventType event_type, |
| const char* label) { |
| ASSERT(label != NULL); |
| set_event_type(event_type); |
| timestamp0_ = 0; |
| timestamp1_ = 0; |
| thread_ = Thread::Current(); |
| label_ = label; |
| FreeArguments(); |
| } |
| |
| |
| static int64_t GetPid(Isolate* isolate) { |
| // Some mapping from Isolate* to an integer process id. |
| // TODO(Cutch): Investigate if process ids can be strings. |
| return static_cast<int64_t>(reinterpret_cast<uintptr_t>(isolate)); |
| } |
| |
| |
| static int64_t GetTid(Thread* thread) { |
| // Some mapping from Thread* to an integer thread id. |
| // TODO(Cutch): Investigate if process ids can be strings. |
| return static_cast<int64_t>(reinterpret_cast<uintptr_t>(thread)); |
| } |
| |
| |
| void TimelineEvent::PrintJSON(JSONStream* stream) const { |
| JSONObject obj(stream); |
| int64_t pid = GetPid(Isolate::Current()); |
| int64_t tid = GetTid(thread_); |
| obj.AddProperty("name", label_); |
| obj.AddProperty("cat", stream_->name()); |
| obj.AddProperty64("tid", tid); |
| obj.AddProperty64("pid", pid); |
| obj.AddPropertyTimeMillis("ts", TimeOrigin()); |
| |
| switch (event_type()) { |
| case kDuration: { |
| obj.AddProperty("ph", "X"); |
| obj.AddPropertyTimeMillis("dur", TimeDuration()); |
| } |
| break; |
| case kInstant: { |
| obj.AddProperty("ph", "i"); |
| obj.AddProperty("s", "p"); |
| } |
| break; |
| case kAsyncBegin: { |
| obj.AddProperty("ph", "b"); |
| obj.AddPropertyF("id", "%" Px64 "", AsyncId()); |
| } |
| break; |
| case kAsyncInstant: { |
| obj.AddProperty("ph", "n"); |
| obj.AddPropertyF("id", "%" Px64 "", AsyncId()); |
| } |
| break; |
| case kAsyncEnd: { |
| obj.AddProperty("ph", "e"); |
| obj.AddPropertyF("id", "%" Px64 "", AsyncId()); |
| } |
| break; |
| default: |
| UNIMPLEMENTED(); |
| } |
| { |
| JSONObject args(&obj, "args"); |
| for (intptr_t i = 0; i < arguments_length_; i++) { |
| const TimelineEventArgument& arg = arguments_[i]; |
| args.AddProperty(arg.name, arg.value); |
| } |
| } |
| } |
| |
| |
| int64_t TimelineEvent::TimeOrigin() const { |
| return timestamp0_; |
| } |
| |
| |
| int64_t TimelineEvent::AsyncId() const { |
| return timestamp1_; |
| } |
| |
| |
| int64_t TimelineEvent::TimeDuration() const { |
| if (timestamp1_ == 0) { |
| // This duration is still open, use current time as end. |
| return OS::GetCurrentTimeMicros() - timestamp0_; |
| } |
| return timestamp1_ - timestamp0_; |
| } |
| |
| |
| TimelineStream::TimelineStream() |
| : recorder_(NULL), |
| name_(NULL), |
| enabled_(false), |
| seq_(0) { |
| } |
| |
| |
| void TimelineStream::Init(const char* name, bool enabled) { |
| name_ = name; |
| enabled_ = enabled; |
| } |
| |
| |
| TimelineEvent* TimelineStream::StartEvent(const Object& obj) { |
| if (!enabled_ || (recorder_ == NULL)) { |
| return NULL; |
| } |
| ASSERT(name_ != NULL); |
| ASSERT(recorder_ != NULL); |
| TimelineEvent* event = recorder_->StartEvent(obj); |
| if (event != NULL) { |
| event->StreamInit(this); |
| } |
| return event; |
| } |
| |
| |
| TimelineEvent* TimelineStream::StartEvent() { |
| if (!enabled_ || (recorder_ == NULL)) { |
| return NULL; |
| } |
| ASSERT(name_ != NULL); |
| TimelineEvent* event = recorder_->StartEvent(); |
| if (event != NULL) { |
| event->StreamInit(this); |
| } |
| return event; |
| } |
| |
| |
| 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, ...) { |
| if (event_ == NULL) { |
| return; |
| } |
| va_list args; |
| va_start(args, fmt); |
| intptr_t len = OS::VSNPrint(NULL, 0, fmt, args); |
| va_end(args); |
| |
| char* buffer = reinterpret_cast<char*>(malloc(len + 1)); |
| va_list args2; |
| va_start(args2, fmt); |
| OS::VSNPrint(buffer, (len + 1), fmt, args2); |
| va_end(args2); |
| |
| event_->SetArgument(i, name, buffer); |
| } |
| |
| |
| TimelineEventRecorder::TimelineEventRecorder() { |
| } |
| |
| |
| void TimelineEventRecorder::PrintJSONMeta(JSONArray* events) const { |
| Isolate* isolate = Isolate::Current(); |
| JSONObject obj(events); |
| int64_t pid = GetPid(isolate); |
| obj.AddProperty("ph", "M"); |
| obj.AddProperty64("pid", pid); |
| obj.AddProperty("name", "process_name"); |
| { |
| JSONObject args(&obj, "args"); |
| args.AddProperty("name", isolate->debugger_name()); |
| } |
| } |
| |
| |
| void TimelineEventRecorder::WriteTo(const char* directory) { |
| Isolate* isolate = Isolate::Current(); |
| |
| Dart_FileOpenCallback file_open = Isolate::file_open_callback(); |
| Dart_FileWriteCallback file_write = Isolate::file_write_callback(); |
| Dart_FileCloseCallback file_close = Isolate::file_close_callback(); |
| if ((file_open == NULL) || (file_write == NULL) || (file_close == NULL)) { |
| return; |
| } |
| |
| JSONStream js; |
| PrintJSON(&js); |
| |
| const char* format = "%s/dart-timeline-%" Pd "-%" 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()); |
| void* file = (*file_open)(filename, true); |
| if (file == NULL) { |
| OS::Print("Failed to write timeline file: %s\n", filename); |
| return; |
| } |
| (*file_write)(js.buffer()->buf(), js.buffer()->length(), file); |
| (*file_close)(file); |
| } |
| |
| |
| intptr_t TimelineEventRingRecorder::SizeForCapacity(intptr_t capacity) { |
| return sizeof(TimelineEvent) * capacity; |
| } |
| |
| |
| TimelineEventRingRecorder::TimelineEventRingRecorder(intptr_t capacity) |
| : events_(NULL), |
| event_objects_(Array::null()), |
| cursor_(0), |
| capacity_(capacity) { |
| if (FLAG_trace_timeline) { |
| // 32-bit: 262,144 bytes per isolate. |
| // 64-bit: 393,216 bytes per isolate. |
| // NOTE: Internal isolates (vm and service) do not have a timeline |
| // event buffer. |
| OS::Print("TimelineEventRingRecorder is %" Pd " bytes (%" Pd " events)\n", |
| SizeForCapacity(capacity), |
| capacity); |
| } |
| events_ = |
| reinterpret_cast<TimelineEvent*>(calloc(capacity, sizeof(TimelineEvent))); |
| const Array& array = Array::Handle(Array::New(capacity, Heap::kOld)); |
| event_objects_ = array.raw(); |
| } |
| |
| |
| TimelineEventRingRecorder::~TimelineEventRingRecorder() { |
| for (intptr_t i = 0; i < capacity_; i++) { |
| // Clear any extra data. |
| events_[i].Reset(); |
| } |
| free(events_); |
| event_objects_ = Array::null(); |
| } |
| |
| |
| void TimelineEventRingRecorder::PrintJSONEvents(JSONArray* events) const { |
| for (intptr_t i = 0; i < capacity_; i++) { |
| if (events_[i].IsValid()) { |
| events->AddValue(&events_[i]); |
| } |
| } |
| } |
| |
| |
| void TimelineEventRingRecorder::PrintJSON(JSONStream* js) const { |
| JSONObject topLevel(js); |
| topLevel.AddProperty("type", "_Timeline"); |
| { |
| JSONArray events(&topLevel, "traceEvents"); |
| PrintJSONMeta(&events); |
| PrintJSONEvents(&events); |
| } |
| } |
| |
| |
| intptr_t TimelineEventRingRecorder::GetNextIndex() { |
| uintptr_t cursor = AtomicOperations::FetchAndIncrement(&cursor_); |
| return cursor % capacity_; |
| } |
| |
| |
| void TimelineEventRingRecorder::VisitObjectPointers( |
| ObjectPointerVisitor* visitor) { |
| visitor->VisitPointer(reinterpret_cast<RawObject**>(&event_objects_)); |
| } |
| |
| |
| TimelineEvent* TimelineEventRingRecorder::StartEvent(const Object& obj) { |
| ASSERT(events_ != NULL); |
| uintptr_t index = GetNextIndex(); |
| const Array& event_objects = Array::Handle(event_objects_); |
| event_objects.SetAt(index, obj); |
| return &events_[index]; |
| } |
| |
| |
| TimelineEvent* TimelineEventRingRecorder::StartEvent() { |
| ASSERT(events_ != NULL); |
| uintptr_t index = GetNextIndex(); |
| return &events_[index]; |
| } |
| |
| void TimelineEventRingRecorder::CompleteEvent(TimelineEvent* event) { |
| ASSERT(events_ != NULL); |
| // no-op. |
| } |
| |
| |
| TimelineEventStreamingRecorder::TimelineEventStreamingRecorder() { |
| } |
| |
| |
| TimelineEventStreamingRecorder::~TimelineEventStreamingRecorder() { |
| } |
| |
| |
| void TimelineEventStreamingRecorder::PrintJSON(JSONStream* js) const { |
| JSONObject topLevel(js); |
| topLevel.AddProperty("type", "_Timeline"); |
| { |
| JSONArray events(&topLevel, "traceEvents"); |
| PrintJSONMeta(&events); |
| } |
| } |
| |
| void TimelineEventStreamingRecorder::VisitObjectPointers( |
| ObjectPointerVisitor* visitor) { |
| // no-op. |
| } |
| |
| |
| TimelineEvent* TimelineEventStreamingRecorder::StartEvent( |
| const Object& object) { |
| // The streaming recorder does not track Dart objects. |
| return StartEvent(); |
| } |
| |
| |
| TimelineEvent* TimelineEventStreamingRecorder::StartEvent() { |
| TimelineEvent* event = new TimelineEvent(); |
| return event; |
| } |
| |
| |
| void TimelineEventStreamingRecorder::CompleteEvent(TimelineEvent* event) { |
| StreamEvent(event); |
| delete event; |
| } |
| |
| } // namespace dart |