|  | // 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 "platform/globals.h" | 
|  | #ifndef PRODUCT | 
|  |  | 
|  | #include "vm/timeline.h" | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <fcntl.h> | 
|  | #include <cstdlib> | 
|  |  | 
|  | #include "platform/atomic.h" | 
|  | #include "vm/isolate.h" | 
|  | #include "vm/json_stream.h" | 
|  | #include "vm/lockers.h" | 
|  | #include "vm/log.h" | 
|  | #include "vm/object.h" | 
|  | #include "vm/service_event.h" | 
|  | #include "vm/thread.h" | 
|  |  | 
|  | namespace dart { | 
|  |  | 
|  | DEFINE_FLAG(bool, complete_timeline, false, "Record the complete timeline"); | 
|  | DEFINE_FLAG(bool, startup_timeline, false, "Record the startup timeline"); | 
|  | DEFINE_FLAG( | 
|  | bool, | 
|  | systrace_timeline, | 
|  | false, | 
|  | "Record the timeline to the platform's tracing service if there is one"); | 
|  | DEFINE_FLAG(bool, trace_timeline, false, "Trace timeline backend"); | 
|  | DEFINE_FLAG(bool, | 
|  | trace_timeline_analysis, | 
|  | false, | 
|  | "Trace timeline analysis backend"); | 
|  | DEFINE_FLAG(bool, | 
|  | timing, | 
|  | false, | 
|  | "Dump isolate timing information from timeline."); | 
|  | DEFINE_FLAG(charp, | 
|  | timeline_dir, | 
|  | NULL, | 
|  | "Enable all timeline trace streams and output VM global trace " | 
|  | "into specified directory."); | 
|  | DEFINE_FLAG(charp, | 
|  | timeline_streams, | 
|  | NULL, | 
|  | "Comma separated list of timeline streams to record. " | 
|  | "Valid values: all, API, Compiler, Dart, Debugger, Embedder, " | 
|  | "GC, Isolate, and VM."); | 
|  | DEFINE_FLAG(charp, | 
|  | timeline_recorder, | 
|  | "ring", | 
|  | "Select the timeline recorder used. " | 
|  | "Valid values: ring, endless, startup, and systrace.") | 
|  |  | 
|  | // Implementation notes: | 
|  | // | 
|  | // Writing events: | 
|  | // |TimelineEvent|s are written into |TimelineEventBlock|s. Each |Thread| caches | 
|  | // a |TimelineEventBlock| object so that it can write events without | 
|  | // synchronizing with other threads in the system. Even though the |Thread| owns | 
|  | // the |TimelineEventBlock| the block may need to be reclaimed by the reporting | 
|  | // system. To support that, a |Thread| must hold its |timeline_block_lock_| | 
|  | // when operating on the |TimelineEventBlock|. This lock will only ever be | 
|  | // busy if blocks are being reclaimed by the reporting system. | 
|  | // | 
|  | // Reporting: | 
|  | // When requested, the timeline is serialized in the trace-event format | 
|  | // (https://goo.gl/hDZw5M). The request can be for a VM-wide timeline or an | 
|  | // isolate specific timeline. In both cases it may be that a thread has | 
|  | // a |TimelineEventBlock| cached in TLS partially filled with events. In order | 
|  | // to report a complete timeline the cached |TimelineEventBlock|s need to be | 
|  | // reclaimed. | 
|  | // | 
|  | // Reclaiming open |TimelineEventBlock|s from threads: | 
|  | // | 
|  | // Each |Thread| can have one |TimelineEventBlock| cached in it. | 
|  | // | 
|  | // To reclaim blocks, we iterate over all threads and remove the cached | 
|  | // |TimelineEventBlock| from each thread. This is safe because we hold the | 
|  | // |Thread|'s |timeline_block_lock_| meaning the block can't be being modified. | 
|  | // | 
|  | // Locking notes: | 
|  | // The following locks are used by the timeline system: | 
|  | // - |TimelineEventRecorder::lock_| This lock is held whenever a | 
|  | // |TimelineEventBlock| is being requested or reclaimed. | 
|  | // - |Thread::timeline_block_lock_| This lock is held whenever a |Thread|'s | 
|  | // cached block is being operated on. | 
|  | // - |Thread::thread_list_lock_| This lock is held when iterating over | 
|  | // |Thread|s. | 
|  | // | 
|  | // Locks must always be taken in the following order: | 
|  | //   |Thread::thread_list_lock_| | 
|  | //     |Thread::timeline_block_lock_| | 
|  | //       |TimelineEventRecorder::lock_| | 
|  | // | 
|  |  | 
|  | static TimelineEventRecorder* CreateTimelineRecorder() { | 
|  | // Some flags require that we use the endless recorder. | 
|  | const bool use_endless_recorder = | 
|  | (FLAG_timeline_dir != NULL) || FLAG_timing || FLAG_complete_timeline; | 
|  |  | 
|  | const bool use_startup_recorder = FLAG_startup_timeline; | 
|  | const bool use_systrace_recorder = FLAG_systrace_timeline; | 
|  |  | 
|  | const char* flag = FLAG_timeline_recorder; | 
|  |  | 
|  | if (use_systrace_recorder || (flag != NULL)) { | 
|  | if (use_systrace_recorder || (strcmp("systrace", flag) == 0)) { | 
|  | if (FLAG_trace_timeline) { | 
|  | THR_Print("Using the Systrace timeline recorder.\n"); | 
|  | } | 
|  |  | 
|  | #if defined(HOST_OS_LINUX) || defined(HOST_OS_ANDROID) | 
|  | return new TimelineEventSystraceRecorder(); | 
|  | #elif defined(HOST_OS_FUCHSIA) | 
|  | return new TimelineEventFuchsiaRecorder(); | 
|  | #else | 
|  | OS::PrintErr( | 
|  | "Warning: The systrace timeline recorder is equivalent to the" | 
|  | "ring recorder on this platform."); | 
|  | return new TimelineEventRingRecorder(); | 
|  | #endif | 
|  | } | 
|  | } | 
|  |  | 
|  | if (use_endless_recorder || (flag != NULL)) { | 
|  | if (use_endless_recorder || (strcmp("endless", flag) == 0)) { | 
|  | if (FLAG_trace_timeline) { | 
|  | THR_Print("Using the endless timeline recorder.\n"); | 
|  | } | 
|  | return new TimelineEventEndlessRecorder(); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (use_startup_recorder || (flag != NULL)) { | 
|  | if (use_startup_recorder || (strcmp("startup", flag) == 0)) { | 
|  | if (FLAG_trace_timeline) { | 
|  | THR_Print("Using the startup recorder.\n"); | 
|  | } | 
|  | return new TimelineEventStartupRecorder(); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (FLAG_trace_timeline) { | 
|  | THR_Print("Using the ring timeline recorder.\n"); | 
|  | } | 
|  |  | 
|  | // Always fall back to the ring recorder. | 
|  | return new TimelineEventRingRecorder(); | 
|  | } | 
|  |  | 
|  | // Returns a caller freed array of stream names in FLAG_timeline_streams. | 
|  | static MallocGrowableArray<char*>* GetEnabledByDefaultTimelineStreams() { | 
|  | MallocGrowableArray<char*>* result = new MallocGrowableArray<char*>(); | 
|  | if (FLAG_timeline_streams == NULL) { | 
|  | // Nothing set. | 
|  | return result; | 
|  | } | 
|  | char* save_ptr;  // Needed for strtok_r. | 
|  | // strtok modifies arg 1 so we make a copy of it. | 
|  | char* streams = strdup(FLAG_timeline_streams); | 
|  | char* token = strtok_r(streams, ",", &save_ptr); | 
|  | while (token != NULL) { | 
|  | result->Add(strdup(token)); | 
|  | token = strtok_r(NULL, ",", &save_ptr); | 
|  | } | 
|  | free(streams); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Frees the result of |GetEnabledByDefaultTimelineStreams|. | 
|  | static void FreeEnabledByDefaultTimelineStreams( | 
|  | MallocGrowableArray<char*>* streams) { | 
|  | if (streams == NULL) { | 
|  | return; | 
|  | } | 
|  | for (intptr_t i = 0; i < streams->length(); i++) { | 
|  | free((*streams)[i]); | 
|  | } | 
|  | delete streams; | 
|  | } | 
|  |  | 
|  | // Returns true if |streams| contains |stream| or "all". Not case sensitive. | 
|  | static bool HasStream(MallocGrowableArray<char*>* streams, const char* stream) { | 
|  | if ((FLAG_timeline_dir != NULL) || FLAG_timing || FLAG_complete_timeline || | 
|  | FLAG_startup_timeline) { | 
|  | return true; | 
|  | } | 
|  | for (intptr_t i = 0; i < streams->length(); i++) { | 
|  | const char* checked_stream = (*streams)[i]; | 
|  | if ((strstr(checked_stream, "all") != NULL) || | 
|  | (strstr(checked_stream, stream) != NULL)) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void Timeline::Init() { | 
|  | ASSERT(recorder_ == NULL); | 
|  | recorder_ = CreateTimelineRecorder(); | 
|  | ASSERT(recorder_ != NULL); | 
|  | enabled_streams_ = GetEnabledByDefaultTimelineStreams(); | 
|  | // Global overrides. | 
|  | #define TIMELINE_STREAM_FLAG_DEFAULT(name, not_used)                           \ | 
|  | stream_##name##_.Init(#name, HasStream(enabled_streams_, #name)); | 
|  | TIMELINE_STREAM_LIST(TIMELINE_STREAM_FLAG_DEFAULT) | 
|  | #undef TIMELINE_STREAM_FLAG_DEFAULT | 
|  |  | 
|  | if (Timeline::stream_Embedder_.enabled() && | 
|  | (Timeline::get_start_recording_cb() != NULL)) { | 
|  | Timeline::get_start_recording_cb()(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Timeline::StreamStateChange(const char* stream_name, | 
|  | bool prev, | 
|  | bool curr) { | 
|  | if (prev == curr) { | 
|  | return; | 
|  | } | 
|  | if (strcmp(stream_name, "Embedder") == 0) { | 
|  | if (curr && (Timeline::get_start_recording_cb() != NULL)) { | 
|  | Timeline::get_start_recording_cb()(); | 
|  | } else if (!curr && (Timeline::get_stop_recording_cb() != NULL)) { | 
|  | Timeline::get_stop_recording_cb()(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void Timeline::Cleanup() { | 
|  | ASSERT(recorder_ != NULL); | 
|  |  | 
|  | if (Timeline::stream_Embedder_.enabled() && | 
|  | (Timeline::get_stop_recording_cb() != NULL)) { | 
|  | Timeline::get_stop_recording_cb()(); | 
|  | } | 
|  |  | 
|  | if (FLAG_timeline_dir != NULL) { | 
|  | recorder_->WriteTo(FLAG_timeline_dir); | 
|  | } | 
|  |  | 
|  | // Disable global streams. | 
|  | #define TIMELINE_STREAM_DISABLE(name, not_used)                                \ | 
|  | Timeline::stream_##name##_.set_enabled(false); | 
|  | TIMELINE_STREAM_LIST(TIMELINE_STREAM_DISABLE) | 
|  | #undef TIMELINE_STREAM_DISABLE | 
|  | delete recorder_; | 
|  | recorder_ = NULL; | 
|  | if (enabled_streams_ != NULL) { | 
|  | FreeEnabledByDefaultTimelineStreams(enabled_streams_); | 
|  | enabled_streams_ = NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | TimelineEventRecorder* Timeline::recorder() { | 
|  | return recorder_; | 
|  | } | 
|  |  | 
|  | void Timeline::ReclaimCachedBlocksFromThreads() { | 
|  | TimelineEventRecorder* recorder = Timeline::recorder(); | 
|  | if (recorder == NULL) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Iterate over threads. | 
|  | OSThreadIterator it; | 
|  | while (it.HasNext()) { | 
|  | OSThread* thread = it.Next(); | 
|  | MutexLocker ml(thread->timeline_block_lock()); | 
|  | // Grab block and clear it. | 
|  | TimelineEventBlock* block = thread->timeline_block(); | 
|  | thread->set_timeline_block(NULL); | 
|  | // TODO(johnmccutchan): Consider dropping the timeline_block_lock here | 
|  | // if we can do it everywhere. This would simplify the lock ordering | 
|  | // requirements. | 
|  | recorder->FinishBlock(block); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Timeline::PrintFlagsToJSON(JSONStream* js) { | 
|  | JSONObject obj(js); | 
|  | obj.AddProperty("type", "TimelineFlags"); | 
|  | TimelineEventRecorder* recorder = Timeline::recorder(); | 
|  | if (recorder == NULL) { | 
|  | obj.AddProperty("recorderName", "null"); | 
|  | } else { | 
|  | obj.AddProperty("recorderName", recorder->name()); | 
|  | } | 
|  | { | 
|  | JSONArray availableStreams(&obj, "availableStreams"); | 
|  | #define ADD_STREAM_NAME(name, not_used) availableStreams.AddValue(#name); | 
|  | TIMELINE_STREAM_LIST(ADD_STREAM_NAME); | 
|  | #undef ADD_STREAM_NAME | 
|  | } | 
|  | { | 
|  | JSONArray recordedStreams(&obj, "recordedStreams"); | 
|  | #define ADD_RECORDED_STREAM_NAME(name, not_used)                               \ | 
|  | if (stream_##name##_.enabled()) {                                            \ | 
|  | recordedStreams.AddValue(#name);                                           \ | 
|  | } | 
|  | TIMELINE_STREAM_LIST(ADD_RECORDED_STREAM_NAME); | 
|  | #undef ADD_RECORDED_STREAM_NAME | 
|  | } | 
|  | } | 
|  |  | 
|  | void Timeline::Clear() { | 
|  | TimelineEventRecorder* recorder = Timeline::recorder(); | 
|  | if (recorder == NULL) { | 
|  | return; | 
|  | } | 
|  | ReclaimCachedBlocksFromThreads(); | 
|  | recorder->Clear(); | 
|  | } | 
|  |  | 
|  | void TimelineEventArguments::SetNumArguments(intptr_t length) { | 
|  | if (length == length_) { | 
|  | return; | 
|  | } | 
|  | if (length == 0) { | 
|  | Free(); | 
|  | return; | 
|  | } | 
|  | if (buffer_ == NULL) { | 
|  | // calloc already nullifies | 
|  | buffer_ = reinterpret_cast<TimelineEventArgument*>( | 
|  | calloc(sizeof(TimelineEventArgument), length)); | 
|  | } else { | 
|  | for (intptr_t i = length; i < length_; ++i) { | 
|  | free(buffer_[i].value); | 
|  | } | 
|  | buffer_ = reinterpret_cast<TimelineEventArgument*>( | 
|  | realloc(buffer_, sizeof(TimelineEventArgument) * length)); | 
|  | if (length > length_) { | 
|  | memset(buffer_ + length_, 0, | 
|  | sizeof(TimelineEventArgument) * (length - length_)); | 
|  | } | 
|  | } | 
|  | length_ = length; | 
|  | } | 
|  |  | 
|  | void TimelineEventArguments::SetArgument(intptr_t i, | 
|  | const char* name, | 
|  | char* argument) { | 
|  | ASSERT(i >= 0); | 
|  | ASSERT(i < length_); | 
|  | buffer_[i].name = name; | 
|  | buffer_[i].value = argument; | 
|  | } | 
|  |  | 
|  | void TimelineEventArguments::CopyArgument(intptr_t i, | 
|  | const char* name, | 
|  | const char* argument) { | 
|  | SetArgument(i, name, strdup(argument)); | 
|  | } | 
|  |  | 
|  | void TimelineEventArguments::FormatArgument(intptr_t i, | 
|  | const char* name, | 
|  | const char* fmt, | 
|  | va_list args) { | 
|  | ASSERT(i >= 0); | 
|  | ASSERT(i < length_); | 
|  | va_list args2; | 
|  | va_copy(args2, args); | 
|  | intptr_t len = Utils::VSNPrint(NULL, 0, fmt, args); | 
|  | va_end(args); | 
|  |  | 
|  | char* buffer = reinterpret_cast<char*>(malloc(len + 1)); | 
|  | Utils::VSNPrint(buffer, (len + 1), fmt, args2); | 
|  | va_end(args2); | 
|  |  | 
|  | SetArgument(i, name, buffer); | 
|  | } | 
|  |  | 
|  | void TimelineEventArguments::StealArguments(TimelineEventArguments* arguments) { | 
|  | Free(); | 
|  | length_ = arguments->length_; | 
|  | buffer_ = arguments->buffer_; | 
|  | arguments->length_ = 0; | 
|  | arguments->buffer_ = NULL; | 
|  | } | 
|  |  | 
|  | void TimelineEventArguments::Free() { | 
|  | if (buffer_ == NULL) { | 
|  | return; | 
|  | } | 
|  | for (intptr_t i = 0; i < length_; i++) { | 
|  | free(buffer_[i].value); | 
|  | } | 
|  | free(buffer_); | 
|  | buffer_ = NULL; | 
|  | length_ = 0; | 
|  | } | 
|  |  | 
|  | TimelineEventRecorder* Timeline::recorder_ = NULL; | 
|  | MallocGrowableArray<char*>* Timeline::enabled_streams_ = NULL; | 
|  | Dart_EmbedderTimelineStartRecording Timeline::start_recording_cb_ = NULL; | 
|  | Dart_EmbedderTimelineStopRecording Timeline::stop_recording_cb_ = NULL; | 
|  |  | 
|  | #define TIMELINE_STREAM_DEFINE(name, enabled_by_default)                       \ | 
|  | TimelineStream Timeline::stream_##name##_; | 
|  | TIMELINE_STREAM_LIST(TIMELINE_STREAM_DEFINE) | 
|  | #undef TIMELINE_STREAM_DEFINE | 
|  |  | 
|  | TimelineEvent::TimelineEvent() | 
|  | : timestamp0_(0), | 
|  | timestamp1_(0), | 
|  | thread_timestamp0_(-1), | 
|  | thread_timestamp1_(-1), | 
|  | state_(0), | 
|  | label_(NULL), | 
|  | category_(""), | 
|  | thread_(OSThread::kInvalidThreadId), | 
|  | isolate_id_(ILLEGAL_PORT) {} | 
|  |  | 
|  | TimelineEvent::~TimelineEvent() { | 
|  | Reset(); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::Reset() { | 
|  | if (owns_label() && label_ != NULL) { | 
|  | free(const_cast<char*>(label_)); | 
|  | } | 
|  | state_ = 0; | 
|  | thread_ = OSThread::kInvalidThreadId; | 
|  | isolate_id_ = ILLEGAL_PORT; | 
|  | category_ = ""; | 
|  | label_ = NULL; | 
|  | arguments_.Free(); | 
|  | set_event_type(kNone); | 
|  | set_pre_serialized_args(false); | 
|  | set_owns_label(false); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::AsyncBegin(const char* label, | 
|  | int64_t async_id, | 
|  | int64_t micros) { | 
|  | Init(kAsyncBegin, label); | 
|  | set_timestamp0(micros); | 
|  | // Overload timestamp1_ with the async_id. | 
|  | set_timestamp1(async_id); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::AsyncInstant(const char* label, | 
|  | int64_t async_id, | 
|  | int64_t micros) { | 
|  | Init(kAsyncInstant, label); | 
|  | set_timestamp0(micros); | 
|  | // Overload timestamp1_ with the async_id. | 
|  | set_timestamp1(async_id); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::AsyncEnd(const char* label, | 
|  | int64_t async_id, | 
|  | int64_t micros) { | 
|  | Init(kAsyncEnd, label); | 
|  | set_timestamp0(micros); | 
|  | // Overload timestamp1_ with the async_id. | 
|  | set_timestamp1(async_id); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::DurationBegin(const char* label, | 
|  | int64_t micros, | 
|  | int64_t thread_micros) { | 
|  | Init(kDuration, label); | 
|  | set_timestamp0(micros); | 
|  | set_thread_timestamp0(thread_micros); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::DurationEnd(int64_t micros, int64_t thread_micros) { | 
|  | ASSERT(timestamp1_ == 0); | 
|  | set_timestamp1(micros); | 
|  | set_thread_timestamp1(thread_micros); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::Instant(const char* label, int64_t micros) { | 
|  | Init(kInstant, label); | 
|  | set_timestamp0(micros); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::Duration(const char* label, | 
|  | int64_t start_micros, | 
|  | int64_t end_micros, | 
|  | int64_t thread_start_micros, | 
|  | int64_t thread_end_micros) { | 
|  | Init(kDuration, label); | 
|  | set_timestamp0(start_micros); | 
|  | set_timestamp1(end_micros); | 
|  | set_thread_timestamp0(thread_start_micros); | 
|  | set_thread_timestamp1(thread_end_micros); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::Begin(const char* label, | 
|  | int64_t micros, | 
|  | int64_t thread_micros) { | 
|  | Init(kBegin, label); | 
|  | set_timestamp0(micros); | 
|  | set_thread_timestamp0(thread_micros); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::End(const char* label, | 
|  | int64_t micros, | 
|  | int64_t thread_micros) { | 
|  | Init(kEnd, label); | 
|  | set_timestamp0(micros); | 
|  | set_thread_timestamp0(thread_micros); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::Counter(const char* label, int64_t micros) { | 
|  | Init(kCounter, label); | 
|  | set_timestamp0(micros); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::FlowBegin(const char* label, | 
|  | int64_t async_id, | 
|  | int64_t micros) { | 
|  | Init(kFlowBegin, label); | 
|  | set_timestamp0(micros); | 
|  | // Overload timestamp1_ with the async_id. | 
|  | set_timestamp1(async_id); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::FlowStep(const char* label, | 
|  | int64_t async_id, | 
|  | int64_t micros) { | 
|  | Init(kFlowStep, label); | 
|  | set_timestamp0(micros); | 
|  | // Overload timestamp1_ with the async_id. | 
|  | set_timestamp1(async_id); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::FlowEnd(const char* label, | 
|  | int64_t async_id, | 
|  | int64_t micros) { | 
|  | Init(kFlowEnd, label); | 
|  | set_timestamp0(micros); | 
|  | // Overload timestamp1_ with the async_id. | 
|  | set_timestamp1(async_id); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::Metadata(const char* label, int64_t micros) { | 
|  | Init(kMetadata, label); | 
|  | set_timestamp0(micros); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::CompleteWithPreSerializedArgs(char* args_json) { | 
|  | set_pre_serialized_args(true); | 
|  | SetNumArguments(1); | 
|  | SetArgument(0, "Dart Arguments", args_json); | 
|  | Complete(); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::FormatArgument(intptr_t i, | 
|  | const char* name, | 
|  | const char* fmt, | 
|  | ...) { | 
|  | va_list args; | 
|  | va_start(args, fmt); | 
|  | arguments_.FormatArgument(i, name, fmt, args); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::Complete() { | 
|  | TimelineEventRecorder* recorder = Timeline::recorder(); | 
|  | if (recorder != NULL) { | 
|  | recorder->CompleteEvent(this); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEvent::StreamInit(TimelineStream* stream) { | 
|  | if (stream != NULL) { | 
|  | category_ = stream->name(); | 
|  | } else { | 
|  | category_ = ""; | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEvent::Init(EventType event_type, const char* label) { | 
|  | ASSERT(label != NULL); | 
|  | state_ = 0; | 
|  | timestamp0_ = 0; | 
|  | timestamp1_ = 0; | 
|  | thread_timestamp0_ = -1; | 
|  | thread_timestamp1_ = -1; | 
|  | OSThread* os_thread = OSThread::Current(); | 
|  | ASSERT(os_thread != NULL); | 
|  | thread_ = os_thread->trace_id(); | 
|  | Isolate* isolate = Isolate::Current(); | 
|  | if (isolate != NULL) { | 
|  | isolate_id_ = isolate->main_port(); | 
|  | } else { | 
|  | isolate_id_ = ILLEGAL_PORT; | 
|  | } | 
|  | label_ = label; | 
|  | arguments_.Free(); | 
|  | set_event_type(event_type); | 
|  | set_pre_serialized_args(false); | 
|  | set_owns_label(false); | 
|  | } | 
|  |  | 
|  | bool TimelineEvent::Within(int64_t time_origin_micros, | 
|  | int64_t time_extent_micros) { | 
|  | if ((time_origin_micros == -1) || (time_extent_micros == -1)) { | 
|  | // No time range specified. | 
|  | return true; | 
|  | } | 
|  | if (IsFinishedDuration()) { | 
|  | // Event is from e_t0 to e_t1. | 
|  | int64_t e_t0 = TimeOrigin(); | 
|  | int64_t e_t1 = TimeEnd(); | 
|  | ASSERT(e_t0 <= e_t1); | 
|  | // Range is from r_t0 to r_t1. | 
|  | int64_t r_t0 = time_origin_micros; | 
|  | int64_t r_t1 = time_origin_micros + time_extent_micros; | 
|  | ASSERT(r_t0 <= r_t1); | 
|  | return !((r_t1 < e_t0) || (e_t1 < r_t0)); | 
|  | } | 
|  | int64_t delta = TimeOrigin() - time_origin_micros; | 
|  | return (delta >= 0) && (delta <= time_extent_micros); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::PrintJSON(JSONStream* stream) const { | 
|  | if (!FLAG_support_service) { | 
|  | return; | 
|  | } | 
|  | JSONObject obj(stream); | 
|  | int64_t pid = OS::ProcessId(); | 
|  | int64_t tid = OSThread::ThreadIdToIntPtr(thread_); | 
|  | obj.AddProperty("name", label_); | 
|  | obj.AddProperty("cat", category_); | 
|  | obj.AddProperty64("tid", tid); | 
|  | obj.AddProperty64("pid", pid); | 
|  | obj.AddPropertyTimeMicros("ts", TimeOrigin()); | 
|  | if (HasThreadCPUTime()) { | 
|  | obj.AddPropertyTimeMicros("tts", ThreadCPUTimeOrigin()); | 
|  | } | 
|  | switch (event_type()) { | 
|  | case kBegin: { | 
|  | obj.AddProperty("ph", "B"); | 
|  | } break; | 
|  | case kEnd: { | 
|  | obj.AddProperty("ph", "E"); | 
|  | } break; | 
|  | case kDuration: { | 
|  | obj.AddProperty("ph", "X"); | 
|  | obj.AddPropertyTimeMicros("dur", TimeDuration()); | 
|  | if (HasThreadCPUTime()) { | 
|  | obj.AddPropertyTimeMicros("tdur", ThreadCPUTimeDuration()); | 
|  | } | 
|  | } 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; | 
|  | case kCounter: { | 
|  | obj.AddProperty("ph", "C"); | 
|  | } break; | 
|  | case kFlowBegin: { | 
|  | obj.AddProperty("ph", "s"); | 
|  | obj.AddPropertyF("id", "%" Px64 "", AsyncId()); | 
|  | } break; | 
|  | case kFlowStep: { | 
|  | obj.AddProperty("ph", "t"); | 
|  | obj.AddPropertyF("id", "%" Px64 "", AsyncId()); | 
|  | } break; | 
|  | case kFlowEnd: { | 
|  | obj.AddProperty("ph", "f"); | 
|  | obj.AddProperty("bp", "e"); | 
|  | obj.AddPropertyF("id", "%" Px64 "", AsyncId()); | 
|  | } break; | 
|  | case kMetadata: { | 
|  | obj.AddProperty("ph", "M"); | 
|  | } break; | 
|  | default: | 
|  | UNIMPLEMENTED(); | 
|  | } | 
|  |  | 
|  | if (pre_serialized_args()) { | 
|  | ASSERT(arguments_.length() == 1); | 
|  | stream->AppendSerializedObject("args", arguments_[0].value); | 
|  | if (isolate_id_ != ILLEGAL_PORT) { | 
|  | // If we have one, append the isolate id. | 
|  | stream->UncloseObject(); | 
|  | stream->PrintfProperty("isolateNumber", "%" Pd64 "", | 
|  | static_cast<int64_t>(isolate_id_)); | 
|  | stream->CloseObject(); | 
|  | } | 
|  | } else { | 
|  | JSONObject args(&obj, "args"); | 
|  | for (intptr_t i = 0; i < arguments_.length(); i++) { | 
|  | const TimelineEventArgument& arg = arguments_[i]; | 
|  | args.AddProperty(arg.name, arg.value); | 
|  | } | 
|  | if (isolate_id_ != ILLEGAL_PORT) { | 
|  | // If we have one, append the isolate id. | 
|  | args.AddPropertyF("isolateNumber", "%" Pd64 "", | 
|  | static_cast<int64_t>(isolate_id_)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | int64_t TimelineEvent::TimeOrigin() const { | 
|  | return timestamp0_; | 
|  | } | 
|  |  | 
|  | int64_t TimelineEvent::AsyncId() const { | 
|  | return timestamp1_; | 
|  | } | 
|  |  | 
|  | int64_t TimelineEvent::LowTime() const { | 
|  | return timestamp0_; | 
|  | } | 
|  |  | 
|  | int64_t TimelineEvent::HighTime() const { | 
|  | if (event_type() == kDuration) { | 
|  | return timestamp1_; | 
|  | } else { | 
|  | return timestamp0_; | 
|  | } | 
|  | } | 
|  |  | 
|  | int64_t TimelineEvent::TimeDuration() const { | 
|  | if (timestamp1_ == 0) { | 
|  | // This duration is still open, use current time as end. | 
|  | return OS::GetCurrentMonotonicMicros() - timestamp0_; | 
|  | } | 
|  | return timestamp1_ - timestamp0_; | 
|  | } | 
|  |  | 
|  | bool TimelineEvent::HasThreadCPUTime() const { | 
|  | return (thread_timestamp0_ != -1); | 
|  | } | 
|  |  | 
|  | int64_t TimelineEvent::ThreadCPUTimeOrigin() const { | 
|  | ASSERT(HasThreadCPUTime()); | 
|  | return thread_timestamp0_; | 
|  | } | 
|  |  | 
|  | int64_t TimelineEvent::ThreadCPUTimeDuration() const { | 
|  | ASSERT(HasThreadCPUTime()); | 
|  | if (thread_timestamp1_ == -1) { | 
|  | // This duration is still open, use current time as end. | 
|  | return OS::GetCurrentThreadCPUMicros() - thread_timestamp0_; | 
|  | } | 
|  | return thread_timestamp1_ - thread_timestamp0_; | 
|  | } | 
|  |  | 
|  | TimelineStream::TimelineStream() : name_(NULL), enabled_(false) {} | 
|  |  | 
|  | void TimelineStream::Init(const char* name, bool enabled) { | 
|  | name_ = name; | 
|  | enabled_ = enabled; | 
|  | } | 
|  |  | 
|  | TimelineEvent* TimelineStream::StartEvent() { | 
|  | TimelineEventRecorder* recorder = Timeline::recorder(); | 
|  | if (!enabled() || (recorder == NULL)) { | 
|  | return NULL; | 
|  | } | 
|  | ASSERT(name_ != NULL); | 
|  | TimelineEvent* event = recorder->StartEvent(); | 
|  | if (event != NULL) { | 
|  | event->StreamInit(this); | 
|  | } | 
|  | return event; | 
|  | } | 
|  |  | 
|  | TimelineEventScope::TimelineEventScope(TimelineStream* stream, | 
|  | const char* label) | 
|  | : StackResource(reinterpret_cast<Thread*>(NULL)), | 
|  | stream_(stream), | 
|  | label_(label), | 
|  | enabled_(false) { | 
|  | Init(); | 
|  | } | 
|  |  | 
|  | TimelineEventScope::TimelineEventScope(Thread* thread, | 
|  | TimelineStream* stream, | 
|  | const char* label) | 
|  | : StackResource(thread), | 
|  | stream_(stream), | 
|  | label_(label), | 
|  | enabled_(false) { | 
|  | Init(); | 
|  | } | 
|  |  | 
|  | TimelineEventScope::~TimelineEventScope() {} | 
|  |  | 
|  | void TimelineEventScope::Init() { | 
|  | ASSERT(enabled_ == false); | 
|  | ASSERT(label_ != NULL); | 
|  | ASSERT(stream_ != NULL); | 
|  | if (!stream_->enabled()) { | 
|  | // Stream is not enabled, do nothing. | 
|  | return; | 
|  | } | 
|  | enabled_ = true; | 
|  | } | 
|  |  | 
|  | void TimelineEventScope::SetNumArguments(intptr_t length) { | 
|  | if (!enabled()) { | 
|  | return; | 
|  | } | 
|  | arguments_.SetNumArguments(length); | 
|  | } | 
|  |  | 
|  | // |name| must be a compile time constant. Takes ownership of |argumentp|. | 
|  | void TimelineEventScope::SetArgument(intptr_t i, | 
|  | const char* name, | 
|  | char* argument) { | 
|  | if (!enabled()) { | 
|  | return; | 
|  | } | 
|  | arguments_.SetArgument(i, name, argument); | 
|  | } | 
|  |  | 
|  | // |name| must be a compile time constant. Copies |argument|. | 
|  | void TimelineEventScope::CopyArgument(intptr_t i, | 
|  | const char* name, | 
|  | const char* argument) { | 
|  | if (!enabled()) { | 
|  | return; | 
|  | } | 
|  | arguments_.CopyArgument(i, name, argument); | 
|  | } | 
|  |  | 
|  | void TimelineEventScope::FormatArgument(intptr_t i, | 
|  | const char* name, | 
|  | const char* fmt, | 
|  | ...) { | 
|  | if (!enabled()) { | 
|  | return; | 
|  | } | 
|  | va_list args; | 
|  | va_start(args, fmt); | 
|  | arguments_.FormatArgument(i, name, fmt, args); | 
|  | } | 
|  |  | 
|  | void TimelineEventScope::StealArguments(TimelineEvent* event) { | 
|  | if (event == NULL) { | 
|  | return; | 
|  | } | 
|  | event->StealArguments(&arguments_); | 
|  | } | 
|  |  | 
|  | TimelineDurationScope::TimelineDurationScope(TimelineStream* stream, | 
|  | const char* label) | 
|  | : TimelineEventScope(stream, label) { | 
|  | if (!FLAG_support_timeline || !enabled()) { | 
|  | return; | 
|  | } | 
|  | timestamp_ = OS::GetCurrentMonotonicMicros(); | 
|  | thread_timestamp_ = OS::GetCurrentThreadCPUMicros(); | 
|  | } | 
|  |  | 
|  | TimelineDurationScope::TimelineDurationScope(Thread* thread, | 
|  | TimelineStream* stream, | 
|  | const char* label) | 
|  | : TimelineEventScope(thread, stream, label) { | 
|  | if (!FLAG_support_timeline || !enabled()) { | 
|  | return; | 
|  | } | 
|  | timestamp_ = OS::GetCurrentMonotonicMicros(); | 
|  | thread_timestamp_ = OS::GetCurrentThreadCPUMicros(); | 
|  | } | 
|  |  | 
|  | TimelineDurationScope::~TimelineDurationScope() { | 
|  | if (!FLAG_support_timeline) { | 
|  | return; | 
|  | } | 
|  | if (!ShouldEmitEvent()) { | 
|  | return; | 
|  | } | 
|  | TimelineEvent* event = stream()->StartEvent(); | 
|  | if (event == NULL) { | 
|  | // Stream is now disabled. | 
|  | return; | 
|  | } | 
|  | ASSERT(event != NULL); | 
|  | // Emit a duration event. | 
|  | event->Duration(label(), timestamp_, OS::GetCurrentMonotonicMicros(), | 
|  | thread_timestamp_, OS::GetCurrentThreadCPUMicros()); | 
|  | StealArguments(event); | 
|  | event->Complete(); | 
|  | } | 
|  |  | 
|  | TimelineBeginEndScope::TimelineBeginEndScope(TimelineStream* stream, | 
|  | const char* label) | 
|  | : TimelineEventScope(stream, label) { | 
|  | if (!FLAG_support_timeline) { | 
|  | return; | 
|  | } | 
|  | EmitBegin(); | 
|  | } | 
|  |  | 
|  | TimelineBeginEndScope::TimelineBeginEndScope(Thread* thread, | 
|  | TimelineStream* stream, | 
|  | const char* label) | 
|  | : TimelineEventScope(thread, stream, label) { | 
|  | if (!FLAG_support_timeline) { | 
|  | return; | 
|  | } | 
|  | EmitBegin(); | 
|  | } | 
|  |  | 
|  | TimelineBeginEndScope::~TimelineBeginEndScope() { | 
|  | if (!FLAG_support_timeline) { | 
|  | return; | 
|  | } | 
|  | EmitEnd(); | 
|  | } | 
|  |  | 
|  | void TimelineBeginEndScope::EmitBegin() { | 
|  | if (!FLAG_support_timeline) { | 
|  | return; | 
|  | } | 
|  | if (!ShouldEmitEvent()) { | 
|  | return; | 
|  | } | 
|  | TimelineEvent* event = stream()->StartEvent(); | 
|  | if (event == NULL) { | 
|  | // Stream is now disabled. | 
|  | set_enabled(false); | 
|  | return; | 
|  | } | 
|  | ASSERT(event != NULL); | 
|  | // Emit a begin event. | 
|  | event->Begin(label()); | 
|  | event->Complete(); | 
|  | } | 
|  |  | 
|  | void TimelineBeginEndScope::EmitEnd() { | 
|  | if (!FLAG_support_timeline) { | 
|  | return; | 
|  | } | 
|  | if (!ShouldEmitEvent()) { | 
|  | return; | 
|  | } | 
|  | TimelineEvent* event = stream()->StartEvent(); | 
|  | if (event == NULL) { | 
|  | // Stream is now disabled. | 
|  | set_enabled(false); | 
|  | return; | 
|  | } | 
|  | ASSERT(event != NULL); | 
|  | // Emit an end event. | 
|  | event->End(label()); | 
|  | StealArguments(event); | 
|  | event->Complete(); | 
|  | } | 
|  |  | 
|  | TimelineEventFilter::TimelineEventFilter(int64_t time_origin_micros, | 
|  | int64_t time_extent_micros) | 
|  | : time_origin_micros_(time_origin_micros), | 
|  | time_extent_micros_(time_extent_micros) { | 
|  | ASSERT(time_origin_micros_ >= -1); | 
|  | ASSERT(time_extent_micros_ >= -1); | 
|  | } | 
|  |  | 
|  | TimelineEventFilter::~TimelineEventFilter() {} | 
|  |  | 
|  | IsolateTimelineEventFilter::IsolateTimelineEventFilter( | 
|  | Dart_Port isolate_id, | 
|  | int64_t time_origin_micros, | 
|  | int64_t time_extent_micros) | 
|  | : TimelineEventFilter(time_origin_micros, time_extent_micros), | 
|  | isolate_id_(isolate_id) {} | 
|  |  | 
|  | TimelineEventRecorder::TimelineEventRecorder() | 
|  | : async_id_(0), time_low_micros_(0), time_high_micros_(0) {} | 
|  |  | 
|  | void TimelineEventRecorder::PrintJSONMeta(JSONArray* events) const { | 
|  | if (!FLAG_support_service) { | 
|  | return; | 
|  | } | 
|  | OSThreadIterator it; | 
|  | while (it.HasNext()) { | 
|  | OSThread* thread = it.Next(); | 
|  | const char* thread_name = thread->name(); | 
|  | if (thread_name == NULL) { | 
|  | // Only emit a thread name if one was set. | 
|  | continue; | 
|  | } | 
|  | JSONObject obj(events); | 
|  | int64_t pid = OS::ProcessId(); | 
|  | int64_t tid = OSThread::ThreadIdToIntPtr(thread->trace_id()); | 
|  | obj.AddProperty("name", "thread_name"); | 
|  | obj.AddProperty("ph", "M"); | 
|  | obj.AddProperty64("pid", pid); | 
|  | obj.AddProperty64("tid", tid); | 
|  | { | 
|  | JSONObject args(&obj, "args"); | 
|  | args.AddPropertyF("name", "%s (%" Pd64 ")", thread_name, tid); | 
|  | args.AddProperty("mode", "basic"); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TimelineEvent* TimelineEventRecorder::ThreadBlockStartEvent() { | 
|  | // Grab the current thread. | 
|  | OSThread* thread = OSThread::Current(); | 
|  | ASSERT(thread != NULL); | 
|  | Mutex* thread_block_lock = thread->timeline_block_lock(); | 
|  | ASSERT(thread_block_lock != NULL); | 
|  | // We are accessing the thread's timeline block- so take the lock here. | 
|  | // This lock will be held until the call to |CompleteEvent| is made. | 
|  | thread_block_lock->Lock(); | 
|  | #if defined(DEBUG) | 
|  | Thread* T = Thread::Current(); | 
|  | if (T != NULL) { | 
|  | T->IncrementNoSafepointScopeDepth(); | 
|  | } | 
|  | #endif  // defined(DEBUG) | 
|  |  | 
|  | TimelineEventBlock* thread_block = thread->timeline_block(); | 
|  |  | 
|  | 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->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->set_timeline_block(thread_block); | 
|  | } | 
|  | if (thread_block != NULL) { | 
|  | // NOTE: We are exiting this function with the thread's block lock held. | 
|  | ASSERT(!thread_block->IsFull()); | 
|  | TimelineEvent* event = thread_block->StartEvent(); | 
|  | return event; | 
|  | } | 
|  | // Drop lock here as no event is being handed out. | 
|  | #if defined(DEBUG) | 
|  | if (T != NULL) { | 
|  | T->DecrementNoSafepointScopeDepth(); | 
|  | } | 
|  | #endif  // defined(DEBUG) | 
|  | thread_block_lock->Unlock(); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | void TimelineEventRecorder::ResetTimeTracking() { | 
|  | time_high_micros_ = 0; | 
|  | time_low_micros_ = kMaxInt64; | 
|  | } | 
|  |  | 
|  | void TimelineEventRecorder::ReportTime(int64_t micros) { | 
|  | if (time_high_micros_ < micros) { | 
|  | time_high_micros_ = micros; | 
|  | } | 
|  | if (time_low_micros_ > micros) { | 
|  | time_low_micros_ = micros; | 
|  | } | 
|  | } | 
|  |  | 
|  | int64_t TimelineEventRecorder::TimeOriginMicros() const { | 
|  | if (time_high_micros_ == 0) { | 
|  | return 0; | 
|  | } | 
|  | return time_low_micros_; | 
|  | } | 
|  |  | 
|  | int64_t TimelineEventRecorder::TimeExtentMicros() const { | 
|  | if (time_high_micros_ == 0) { | 
|  | return 0; | 
|  | } | 
|  | return time_high_micros_ - time_low_micros_; | 
|  | } | 
|  |  | 
|  | void TimelineEventRecorder::ThreadBlockCompleteEvent(TimelineEvent* event) { | 
|  | if (event == NULL) { | 
|  | return; | 
|  | } | 
|  | // Grab the current thread. | 
|  | OSThread* thread = OSThread::Current(); | 
|  | ASSERT(thread != NULL); | 
|  | // Unlock the thread's block lock. | 
|  | Mutex* thread_block_lock = thread->timeline_block_lock(); | 
|  | ASSERT(thread_block_lock != NULL); | 
|  | #if defined(DEBUG) | 
|  | Thread* T = Thread::Current(); | 
|  | if (T != NULL) { | 
|  | T->DecrementNoSafepointScopeDepth(); | 
|  | } | 
|  | #endif  // defined(DEBUG) | 
|  | thread_block_lock->Unlock(); | 
|  | } | 
|  |  | 
|  | void TimelineEventRecorder::WriteTo(const char* directory) { | 
|  | if (!FLAG_support_service) { | 
|  | return; | 
|  | } | 
|  | Dart_FileOpenCallback file_open = Dart::file_open_callback(); | 
|  | Dart_FileWriteCallback file_write = Dart::file_write_callback(); | 
|  | Dart_FileCloseCallback file_close = Dart::file_close_callback(); | 
|  | if ((file_open == NULL) || (file_write == NULL) || (file_close == NULL)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | Timeline::ReclaimCachedBlocksFromThreads(); | 
|  |  | 
|  | intptr_t pid = OS::ProcessId(); | 
|  | char* filename = | 
|  | OS::SCreate(NULL, "%s/dart-timeline-%" Pd ".json", directory, pid); | 
|  | void* file = (*file_open)(filename, true); | 
|  | if (file == NULL) { | 
|  | OS::PrintErr("Failed to write timeline file: %s\n", filename); | 
|  | free(filename); | 
|  | return; | 
|  | } | 
|  | free(filename); | 
|  |  | 
|  | JSONStream js; | 
|  | TimelineEventFilter filter; | 
|  | PrintTraceEvent(&js, &filter); | 
|  | // Steal output from JSONStream. | 
|  | char* output = NULL; | 
|  | intptr_t output_length = 0; | 
|  | js.Steal(&output, &output_length); | 
|  | (*file_write)(output, output_length, file); | 
|  | // Free the stolen output. | 
|  | free(output); | 
|  | (*file_close)(file); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | int64_t TimelineEventRecorder::GetNextAsyncId() { | 
|  | // TODO(johnmccutchan): Gracefully handle wrap around. | 
|  | // TODO(rmacnak): Use TRACE_NONCE() on Fuchsia? | 
|  | uint32_t next = | 
|  | static_cast<uint32_t>(AtomicOperations::FetchAndIncrement(&async_id_)); | 
|  | return static_cast<int64_t>(next); | 
|  | } | 
|  |  | 
|  | void TimelineEventRecorder::FinishBlock(TimelineEventBlock* block) { | 
|  | if (block == NULL) { | 
|  | return; | 
|  | } | 
|  | MutexLocker ml(&lock_); | 
|  | block->Finish(); | 
|  | } | 
|  |  | 
|  | TimelineEventBlock* TimelineEventRecorder::GetNewBlock() { | 
|  | MutexLocker ml(&lock_); | 
|  | return GetNewBlockLocked(); | 
|  | } | 
|  |  | 
|  | TimelineEventFixedBufferRecorder::TimelineEventFixedBufferRecorder( | 
|  | intptr_t capacity) | 
|  | : memory_(NULL), | 
|  | blocks_(NULL), | 
|  | capacity_(capacity), | 
|  | num_blocks_(0), | 
|  | block_cursor_(0) { | 
|  | // Capacity must be a multiple of TimelineEventBlock::kBlockSize | 
|  | ASSERT((capacity % TimelineEventBlock::kBlockSize) == 0); | 
|  | // Allocate blocks array. | 
|  | num_blocks_ = capacity / TimelineEventBlock::kBlockSize; | 
|  |  | 
|  | intptr_t size = Utils::RoundUp(num_blocks_ * sizeof(TimelineEventBlock), | 
|  | VirtualMemory::PageSize()); | 
|  | const bool kNotExecutable = false; | 
|  | memory_ = VirtualMemory::Allocate(size, kNotExecutable, "dart-timeline"); | 
|  | if (memory_ == NULL) { | 
|  | OUT_OF_MEMORY(); | 
|  | } | 
|  | blocks_ = reinterpret_cast<TimelineEventBlock*>(memory_->address()); | 
|  | } | 
|  |  | 
|  | TimelineEventFixedBufferRecorder::~TimelineEventFixedBufferRecorder() { | 
|  | // Delete all blocks. | 
|  | for (intptr_t i = 0; i < num_blocks_; i++) { | 
|  | blocks_[i].Reset(); | 
|  | } | 
|  | delete memory_; | 
|  | } | 
|  |  | 
|  | void TimelineEventFixedBufferRecorder::PrintJSONEvents( | 
|  | JSONArray* events, | 
|  | TimelineEventFilter* filter) { | 
|  | if (!FLAG_support_service) { | 
|  | return; | 
|  | } | 
|  | MutexLocker ml(&lock_); | 
|  | ResetTimeTracking(); | 
|  | intptr_t block_offset = FindOldestBlockIndex(); | 
|  | if (block_offset == -1) { | 
|  | // All blocks are empty. | 
|  | return; | 
|  | } | 
|  | for (intptr_t block_idx = 0; block_idx < num_blocks_; block_idx++) { | 
|  | TimelineEventBlock* block = | 
|  | &blocks_[(block_idx + block_offset) % num_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 (filter->IncludeEvent(event) && | 
|  | event->Within(filter->time_origin_micros(), | 
|  | filter->time_extent_micros())) { | 
|  | ReportTime(event->LowTime()); | 
|  | ReportTime(event->HighTime()); | 
|  | events->AddValue(event); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEventFixedBufferRecorder::PrintJSON(JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | if (!FLAG_support_service) { | 
|  | return; | 
|  | } | 
|  | JSONObject topLevel(js); | 
|  | topLevel.AddProperty("type", "_Timeline"); | 
|  | { | 
|  | JSONArray events(&topLevel, "traceEvents"); | 
|  | PrintJSONMeta(&events); | 
|  | PrintJSONEvents(&events, filter); | 
|  | } | 
|  | topLevel.AddPropertyTimeMicros("timeOriginMicros", TimeOriginMicros()); | 
|  | topLevel.AddPropertyTimeMicros("timeExtentMicros", TimeExtentMicros()); | 
|  | } | 
|  |  | 
|  | void TimelineEventFixedBufferRecorder::PrintTraceEvent( | 
|  | JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | if (!FLAG_support_service) { | 
|  | return; | 
|  | } | 
|  | JSONArray events(js); | 
|  | PrintJSONMeta(&events); | 
|  | PrintJSONEvents(&events, filter); | 
|  | } | 
|  |  | 
|  | TimelineEventBlock* TimelineEventFixedBufferRecorder::GetHeadBlockLocked() { | 
|  | return &blocks_[0]; | 
|  | } | 
|  |  | 
|  | void TimelineEventFixedBufferRecorder::Clear() { | 
|  | MutexLocker ml(&lock_); | 
|  | for (intptr_t i = 0; i < num_blocks_; i++) { | 
|  | TimelineEventBlock* block = &blocks_[i]; | 
|  | block->Reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | intptr_t TimelineEventFixedBufferRecorder::FindOldestBlockIndex() const { | 
|  | int64_t earliest_time = kMaxInt64; | 
|  | intptr_t earliest_index = -1; | 
|  | for (intptr_t block_idx = 0; block_idx < num_blocks_; block_idx++) { | 
|  | TimelineEventBlock* block = &blocks_[block_idx]; | 
|  | if (block->IsEmpty()) { | 
|  | // Skip empty blocks. | 
|  | continue; | 
|  | } | 
|  | if (block->LowerTimeBound() < earliest_time) { | 
|  | earliest_time = block->LowerTimeBound(); | 
|  | earliest_index = block_idx; | 
|  | } | 
|  | } | 
|  | return earliest_index; | 
|  | } | 
|  |  | 
|  | TimelineEvent* TimelineEventFixedBufferRecorder::StartEvent() { | 
|  | return ThreadBlockStartEvent(); | 
|  | } | 
|  |  | 
|  | void TimelineEventFixedBufferRecorder::CompleteEvent(TimelineEvent* event) { | 
|  | if (event == NULL) { | 
|  | return; | 
|  | } | 
|  | ThreadBlockCompleteEvent(event); | 
|  | } | 
|  |  | 
|  | TimelineEventBlock* TimelineEventRingRecorder::GetNewBlockLocked() { | 
|  | // 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(); | 
|  | return block; | 
|  | } | 
|  |  | 
|  | TimelineEventBlock* TimelineEventStartupRecorder::GetNewBlockLocked() { | 
|  | if (block_cursor_ == num_blocks_) { | 
|  | return NULL; | 
|  | } | 
|  | TimelineEventBlock* block = &blocks_[block_cursor_++]; | 
|  | block->Reset(); | 
|  | block->Open(); | 
|  | return block; | 
|  | } | 
|  |  | 
|  | TimelineEventCallbackRecorder::TimelineEventCallbackRecorder() {} | 
|  |  | 
|  | TimelineEventCallbackRecorder::~TimelineEventCallbackRecorder() {} | 
|  |  | 
|  | void TimelineEventCallbackRecorder::PrintJSON(JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | if (!FLAG_support_service) { | 
|  | return; | 
|  | } | 
|  | JSONObject topLevel(js); | 
|  | topLevel.AddProperty("type", "_Timeline"); | 
|  | { | 
|  | JSONArray events(&topLevel, "traceEvents"); | 
|  | PrintJSONMeta(&events); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEventCallbackRecorder::PrintTraceEvent( | 
|  | JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | if (!FLAG_support_service) { | 
|  | return; | 
|  | } | 
|  | JSONArray events(js); | 
|  | } | 
|  |  | 
|  | TimelineEvent* TimelineEventCallbackRecorder::StartEvent() { | 
|  | TimelineEvent* event = new TimelineEvent(); | 
|  | return event; | 
|  | } | 
|  |  | 
|  | void TimelineEventCallbackRecorder::CompleteEvent(TimelineEvent* event) { | 
|  | OnEvent(event); | 
|  | delete event; | 
|  | } | 
|  |  | 
|  | TimelineEventPlatformRecorder::TimelineEventPlatformRecorder() {} | 
|  |  | 
|  | TimelineEventPlatformRecorder::~TimelineEventPlatformRecorder() {} | 
|  |  | 
|  | void TimelineEventPlatformRecorder::PrintJSON(JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | if (!FLAG_support_service) { | 
|  | return; | 
|  | } | 
|  | JSONObject topLevel(js); | 
|  | topLevel.AddProperty("type", "_Timeline"); | 
|  | { | 
|  | JSONArray events(&topLevel, "traceEvents"); | 
|  | PrintJSONMeta(&events); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEventPlatformRecorder::PrintTraceEvent( | 
|  | JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | if (!FLAG_support_service) { | 
|  | return; | 
|  | } | 
|  | JSONArray events(js); | 
|  | } | 
|  |  | 
|  | TimelineEvent* TimelineEventPlatformRecorder::StartEvent() { | 
|  | TimelineEvent* event = new TimelineEvent(); | 
|  | return event; | 
|  | } | 
|  |  | 
|  | void TimelineEventPlatformRecorder::CompleteEvent(TimelineEvent* event) { | 
|  | OnEvent(event); | 
|  | delete event; | 
|  | } | 
|  |  | 
|  | TimelineEventEndlessRecorder::TimelineEventEndlessRecorder() | 
|  | : head_(NULL), block_index_(0) {} | 
|  |  | 
|  | TimelineEventEndlessRecorder::~TimelineEventEndlessRecorder() { | 
|  | TimelineEventBlock* current = head_; | 
|  | head_ = NULL; | 
|  |  | 
|  | while (current != NULL) { | 
|  | TimelineEventBlock* next = current->next(); | 
|  | delete current; | 
|  | current = next; | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEventEndlessRecorder::PrintJSON(JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | if (!FLAG_support_service) { | 
|  | return; | 
|  | } | 
|  | JSONObject topLevel(js); | 
|  | topLevel.AddProperty("type", "_Timeline"); | 
|  | { | 
|  | JSONArray events(&topLevel, "traceEvents"); | 
|  | PrintJSONMeta(&events); | 
|  | PrintJSONEvents(&events, filter); | 
|  | } | 
|  | topLevel.AddPropertyTimeMicros("timeOriginMicros", TimeOriginMicros()); | 
|  | topLevel.AddPropertyTimeMicros("timeExtentMicros", TimeExtentMicros()); | 
|  | } | 
|  |  | 
|  | void TimelineEventEndlessRecorder::PrintTraceEvent( | 
|  | JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | if (!FLAG_support_service) { | 
|  | return; | 
|  | } | 
|  | JSONArray events(js); | 
|  | PrintJSONMeta(&events); | 
|  | PrintJSONEvents(&events, filter); | 
|  | } | 
|  |  | 
|  | TimelineEventBlock* TimelineEventEndlessRecorder::GetHeadBlockLocked() { | 
|  | return head_; | 
|  | } | 
|  |  | 
|  | TimelineEvent* TimelineEventEndlessRecorder::StartEvent() { | 
|  | return ThreadBlockStartEvent(); | 
|  | } | 
|  |  | 
|  | void TimelineEventEndlessRecorder::CompleteEvent(TimelineEvent* event) { | 
|  | if (event == NULL) { | 
|  | return; | 
|  | } | 
|  | ThreadBlockCompleteEvent(event); | 
|  | } | 
|  |  | 
|  | TimelineEventBlock* TimelineEventEndlessRecorder::GetNewBlockLocked() { | 
|  | TimelineEventBlock* block = new TimelineEventBlock(block_index_++); | 
|  | block->set_next(head_); | 
|  | block->Open(); | 
|  | head_ = block; | 
|  | if (FLAG_trace_timeline) { | 
|  | OS::PrintErr("Created new block %p\n", block); | 
|  | } | 
|  | return head_; | 
|  | } | 
|  |  | 
|  | static int TimelineEventBlockCompare(TimelineEventBlock* const* a, | 
|  | TimelineEventBlock* const* b) { | 
|  | return (*a)->LowerTimeBound() - (*b)->LowerTimeBound(); | 
|  | } | 
|  |  | 
|  | void TimelineEventEndlessRecorder::PrintJSONEvents( | 
|  | JSONArray* events, | 
|  | TimelineEventFilter* filter) { | 
|  | if (!FLAG_support_service) { | 
|  | return; | 
|  | } | 
|  | MutexLocker ml(&lock_); | 
|  | ResetTimeTracking(); | 
|  | // Collect all interesting blocks. | 
|  | MallocGrowableArray<TimelineEventBlock*> blocks(8); | 
|  | TimelineEventBlock* current = head_; | 
|  | while (current != NULL) { | 
|  | if (filter->IncludeBlock(current)) { | 
|  | blocks.Add(current); | 
|  | } | 
|  | current = current->next(); | 
|  | } | 
|  | // Bail early. | 
|  | if (blocks.length() == 0) { | 
|  | return; | 
|  | } | 
|  | // Sort the interesting blocks so that blocks with earlier events are | 
|  | // outputted first. | 
|  | blocks.Sort(TimelineEventBlockCompare); | 
|  | // Output blocks in sorted order. | 
|  | for (intptr_t block_idx = 0; block_idx < blocks.length(); block_idx++) { | 
|  | current = blocks[block_idx]; | 
|  | intptr_t length = current->length(); | 
|  | for (intptr_t i = 0; i < length; i++) { | 
|  | TimelineEvent* event = current->At(i); | 
|  | if (filter->IncludeEvent(event) && | 
|  | event->Within(filter->time_origin_micros(), | 
|  | filter->time_extent_micros())) { | 
|  | ReportTime(event->LowTime()); | 
|  | ReportTime(event->HighTime()); | 
|  | events->AddValue(event); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEventEndlessRecorder::Clear() { | 
|  | TimelineEventBlock* current = head_; | 
|  | while (current != NULL) { | 
|  | TimelineEventBlock* next = current->next(); | 
|  | delete current; | 
|  | current = next; | 
|  | } | 
|  | head_ = NULL; | 
|  | block_index_ = 0; | 
|  | OSThread* thread = OSThread::Current(); | 
|  | thread->set_timeline_block(NULL); | 
|  | } | 
|  |  | 
|  | TimelineEventBlock::TimelineEventBlock(intptr_t block_index) | 
|  | : next_(NULL), | 
|  | length_(0), | 
|  | block_index_(block_index), | 
|  | thread_id_(OSThread::kInvalidThreadId), | 
|  | in_use_(false) {} | 
|  |  | 
|  | TimelineEventBlock::~TimelineEventBlock() { | 
|  | Reset(); | 
|  | } | 
|  |  | 
|  | void TimelineEventBlock::PrintJSON(JSONStream* js) const { | 
|  | ASSERT(!in_use()); | 
|  | JSONArray events(js); | 
|  | for (intptr_t i = 0; i < length(); i++) { | 
|  | const TimelineEvent* event = At(i); | 
|  | events.AddValue(event); | 
|  | } | 
|  | } | 
|  |  | 
|  | TimelineEvent* TimelineEventBlock::StartEvent() { | 
|  | ASSERT(!IsFull()); | 
|  | if (FLAG_trace_timeline) { | 
|  | OSThread* os_thread = OSThread::Current(); | 
|  | ASSERT(os_thread != NULL); | 
|  | intptr_t tid = OSThread::ThreadIdToIntPtr(os_thread->id()); | 
|  | OS::PrintErr("StartEvent in block %p for thread %" Pd "\n", this, tid); | 
|  | } | 
|  | return &events_[length_++]; | 
|  | } | 
|  |  | 
|  | int64_t TimelineEventBlock::LowerTimeBound() const { | 
|  | if (length_ == 0) { | 
|  | return kMaxInt64; | 
|  | } | 
|  | ASSERT(length_ > 0); | 
|  | return events_[0].TimeOrigin(); | 
|  | } | 
|  |  | 
|  | bool TimelineEventBlock::CheckBlock() { | 
|  | if (length() == 0) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | for (intptr_t i = 0; i < length(); i++) { | 
|  | if (At(i)->thread() != thread_id()) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | // - events have monotonically increasing timestamps. | 
|  | int64_t last_time = LowerTimeBound(); | 
|  | for (intptr_t i = 0; i < length(); i++) { | 
|  | if (last_time > At(i)->TimeOrigin()) { | 
|  | return false; | 
|  | } | 
|  | last_time = At(i)->TimeOrigin(); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void TimelineEventBlock::Reset() { | 
|  | for (intptr_t i = 0; i < kBlockSize; i++) { | 
|  | // Clear any extra data. | 
|  | events_[i].Reset(); | 
|  | } | 
|  | length_ = 0; | 
|  | thread_id_ = OSThread::kInvalidThreadId; | 
|  | in_use_ = false; | 
|  | } | 
|  |  | 
|  | void TimelineEventBlock::Open() { | 
|  | OSThread* os_thread = OSThread::Current(); | 
|  | ASSERT(os_thread != NULL); | 
|  | thread_id_ = os_thread->trace_id(); | 
|  | in_use_ = true; | 
|  | } | 
|  |  | 
|  | void TimelineEventBlock::Finish() { | 
|  | if (FLAG_trace_timeline) { | 
|  | OS::PrintErr("Finish block %p\n", this); | 
|  | } | 
|  | in_use_ = false; | 
|  | if (Service::timeline_stream.enabled()) { | 
|  | ServiceEvent service_event(NULL, ServiceEvent::kTimelineEvents); | 
|  | service_event.set_timeline_event_block(this); | 
|  | Service::HandleEvent(&service_event); | 
|  | } | 
|  | } | 
|  |  | 
|  | TimelineEventBlockIterator::TimelineEventBlockIterator( | 
|  | TimelineEventRecorder* recorder) | 
|  | : current_(NULL), recorder_(NULL) { | 
|  | Reset(recorder); | 
|  | } | 
|  |  | 
|  | TimelineEventBlockIterator::~TimelineEventBlockIterator() { | 
|  | Reset(NULL); | 
|  | } | 
|  |  | 
|  | void TimelineEventBlockIterator::Reset(TimelineEventRecorder* recorder) { | 
|  | // Clear current. | 
|  | current_ = NULL; | 
|  | if (recorder_ != NULL) { | 
|  | // Unlock old recorder. | 
|  | recorder_->lock_.Unlock(); | 
|  | } | 
|  | recorder_ = recorder; | 
|  | if (recorder_ == NULL) { | 
|  | return; | 
|  | } | 
|  | // Lock new recorder. | 
|  | recorder_->lock_.Lock(); | 
|  | // Queue up first block. | 
|  | current_ = recorder_->GetHeadBlockLocked(); | 
|  | } | 
|  |  | 
|  | bool TimelineEventBlockIterator::HasNext() const { | 
|  | return current_ != NULL; | 
|  | } | 
|  |  | 
|  | TimelineEventBlock* TimelineEventBlockIterator::Next() { | 
|  | ASSERT(current_ != NULL); | 
|  | TimelineEventBlock* r = current_; | 
|  | current_ = current_->next(); | 
|  | return r; | 
|  | } | 
|  |  | 
|  | void DartTimelineEventHelpers::ReportTaskEvent(Thread* thread, | 
|  | TimelineEvent* event, | 
|  | int64_t start, | 
|  | int64_t id, | 
|  | const char* phase, | 
|  | const char* category, | 
|  | char* name, | 
|  | char* args) { | 
|  | ASSERT(phase != NULL); | 
|  | ASSERT((phase[0] == 'n') || (phase[0] == 'b') || (phase[0] == 'e')); | 
|  | ASSERT(phase[1] == '\0'); | 
|  | switch (phase[0]) { | 
|  | case 'n': | 
|  | event->AsyncInstant(name, id, start); | 
|  | break; | 
|  | case 'b': | 
|  | event->AsyncBegin(name, id, start); | 
|  | break; | 
|  | case 'e': | 
|  | event->AsyncEnd(name, id, start); | 
|  | break; | 
|  | default: | 
|  | UNREACHABLE(); | 
|  | } | 
|  | event->set_owns_label(true); | 
|  | event->CompleteWithPreSerializedArgs(args); | 
|  | } | 
|  |  | 
|  | void DartTimelineEventHelpers::ReportCompleteEvent(Thread* thread, | 
|  | TimelineEvent* event, | 
|  | int64_t start, | 
|  | int64_t start_cpu, | 
|  | const char* category, | 
|  | char* name, | 
|  | char* args) { | 
|  | const int64_t end = OS::GetCurrentMonotonicMicros(); | 
|  | const int64_t end_cpu = OS::GetCurrentThreadCPUMicros(); | 
|  | event->Duration(name, start, end, start_cpu, end_cpu); | 
|  | event->set_owns_label(true); | 
|  | event->CompleteWithPreSerializedArgs(args); | 
|  | } | 
|  |  | 
|  | void DartTimelineEventHelpers::ReportFlowEvent(Thread* thread, | 
|  | TimelineEvent* event, | 
|  | int64_t start, | 
|  | int64_t start_cpu, | 
|  | const char* category, | 
|  | char* name, | 
|  | int64_t type, | 
|  | int64_t flow_id, | 
|  | char* args) { | 
|  | TimelineEvent::EventType event_type = | 
|  | static_cast<TimelineEvent::EventType>(type); | 
|  | switch (event_type) { | 
|  | case TimelineEvent::kFlowBegin: | 
|  | event->FlowBegin(name, flow_id, start); | 
|  | break; | 
|  | case TimelineEvent::kFlowStep: | 
|  | event->FlowStep(name, flow_id, start); | 
|  | break; | 
|  | case TimelineEvent::kFlowEnd: | 
|  | event->FlowEnd(name, flow_id, start); | 
|  | break; | 
|  | default: | 
|  | UNREACHABLE(); | 
|  | break; | 
|  | } | 
|  | event->set_owns_label(true); | 
|  | event->CompleteWithPreSerializedArgs(args); | 
|  | } | 
|  |  | 
|  | void DartTimelineEventHelpers::ReportInstantEvent(Thread* thread, | 
|  | TimelineEvent* event, | 
|  | int64_t start, | 
|  | const char* category, | 
|  | char* name, | 
|  | char* args) { | 
|  | event->Instant(name, start); | 
|  | event->set_owns_label(true); | 
|  | event->CompleteWithPreSerializedArgs(args); | 
|  | } | 
|  |  | 
|  | }  // namespace dart | 
|  |  | 
|  | #endif  // !PRODUCT |