|  | // 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 "vm/globals.h" | 
|  | #if defined(SUPPORT_TIMELINE) | 
|  |  | 
|  | #include "vm/timeline.h" | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <fcntl.h> | 
|  |  | 
|  | #include <cstdlib> | 
|  | #include <functional> | 
|  | #include <memory> | 
|  | #include <tuple> | 
|  | #include <utility> | 
|  |  | 
|  | #if defined(DART_HOST_OS_MACOS) | 
|  | #include <os/signpost.h> | 
|  | #endif | 
|  |  | 
|  | #include "platform/atomic.h" | 
|  | #include "platform/hashmap.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.h" | 
|  | #include "vm/service_event.h" | 
|  | #include "vm/thread.h" | 
|  |  | 
|  | #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  | #include "perfetto/ext/tracing/core/trace_packet.h" | 
|  | #include "vm/perfetto_utils.h" | 
|  | #include "vm/protos/perfetto/common/builtin_clock.pbzero.h" | 
|  | #include "vm/protos/perfetto/trace/clock_snapshot.pbzero.h" | 
|  | #include "vm/protos/perfetto/trace/interned_data/interned_data.pbzero.h" | 
|  | #include "vm/protos/perfetto/trace/trace_packet.pbzero.h" | 
|  | #include "vm/protos/perfetto/trace/track_event/debug_annotation.pbzero.h" | 
|  | #include "vm/protos/perfetto/trace/track_event/process_descriptor.pbzero.h" | 
|  | #include "vm/protos/perfetto/trace/track_event/thread_descriptor.pbzero.h" | 
|  | #include "vm/protos/perfetto/trace/track_event/track_descriptor.pbzero.h" | 
|  | #include "vm/protos/perfetto/trace/track_event/track_event.pbzero.h" | 
|  | #endif  // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  |  | 
|  | namespace dart { | 
|  |  | 
|  | #if defined(PRODUCT) | 
|  | #define DEFAULT_TIMELINE_RECORDER "none" | 
|  | #define SUPPORTED_TIMELINE_RECORDERS "systrace, file, callback" | 
|  | #else | 
|  | #define DEFAULT_TIMELINE_RECORDER "ring" | 
|  | #if defined(SUPPORT_PERFETTO) | 
|  | #define SUPPORTED_TIMELINE_RECORDERS                                           \ | 
|  | "ring, endless, startup, systrace, file, callback, perfettofile" | 
|  | #else | 
|  | #define SUPPORTED_TIMELINE_RECORDERS                                           \ | 
|  | "ring, endless, startup, systrace, file, callback" | 
|  | #endif | 
|  | #endif | 
|  |  | 
|  | DEFINE_FLAG(bool, complete_timeline, false, "Record the complete timeline"); | 
|  | DEFINE_FLAG(bool, startup_timeline, false, "Record the startup timeline"); | 
|  | // TODO(derekx): Remove this flag in Dart 3.4. | 
|  | 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(charp, | 
|  | timeline_dir, | 
|  | nullptr, | 
|  | "Enable all timeline trace streams and output VM global trace " | 
|  | "into specified directory. This flag is ignored by the file and " | 
|  | "perfetto recorders."); | 
|  | DEFINE_FLAG(charp, | 
|  | timeline_streams, | 
|  | nullptr, | 
|  | "Comma separated list of timeline streams to record. " | 
|  | "Valid values: all, API, Compiler, CompilerVerbose, Dart, " | 
|  | "Debugger, Embedder, GC, Isolate, Microtask, and VM."); | 
|  | DEFINE_FLAG(charp, | 
|  | timeline_recorder, | 
|  | DEFAULT_TIMELINE_RECORDER, | 
|  | "Select the timeline recorder used. " | 
|  | "Valid values: none, " SUPPORTED_TIMELINE_RECORDERS) | 
|  | DEFINE_FLAG(bool, | 
|  | intern_strings_when_writing_perfetto_timeline, | 
|  | false, | 
|  | "Intern strings when writing timeline in perfetto format.") | 
|  |  | 
|  | // 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 either Chrome's JSON trace | 
|  | // format (https://goo.gl/hDZw5M) or Perfetto's proto trace format. 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. | 
|  | // When clearing the reclaimed blocks, or serializing the events in them, we | 
|  | // hold |TimelineEventRecorder::lock_| before reclaiming the blocks, to prevent | 
|  | // reclaimed blocks from being handed out again until we release it. | 
|  | // | 
|  | // 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_| | 
|  | //     |TimelineEventRecorder::lock_| | 
|  | //       |Thread::timeline_block_lock_| | 
|  | // | 
|  |  | 
|  | std::atomic<RecorderSynchronizationLock::RecorderState> | 
|  | RecorderSynchronizationLock::recorder_state_ = { | 
|  | RecorderSynchronizationLock::kUninitialized}; | 
|  | std::atomic<intptr_t> RecorderSynchronizationLock::outstanding_event_writes_ = { | 
|  | 0}; | 
|  |  | 
|  | static TimelineEventRecorder* CreateDefaultTimelineRecorder() { | 
|  | #if defined(PRODUCT) | 
|  | return new TimelineEventNopRecorder(); | 
|  | #else | 
|  | return new TimelineEventRingRecorder(); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | #if !defined(PRODUCT) && defined(SUPPORT_PERFETTO) | 
|  | static TimelineEventRecorder* CreateTimelineEventPerfettoFileRecorder( | 
|  | const char* filename); | 
|  | #endif | 
|  |  | 
|  | static TimelineEventRecorder* CreateTimelineRecorder() { | 
|  | ASSERT(FLAG_timeline_recorder != nullptr); | 
|  | const char* flag = FLAG_timeline_recorder; | 
|  |  | 
|  | if (FLAG_systrace_timeline) { | 
|  | OS::PrintErr( | 
|  | "Warning: the --systrace-timeline flag is deprecated and will " | 
|  | "be removed in Dart SDK v3.4. Please use --timeline-recorder=systrace " | 
|  | "instead.\n"); | 
|  | flag = "systrace"; | 
|  | } else if (FLAG_timeline_dir != nullptr || FLAG_complete_timeline) { | 
|  | // Some flags require that we use the endless recorder. | 
|  | flag = "endless"; | 
|  | } else if (FLAG_startup_timeline) { | 
|  | flag = "startup"; | 
|  | } | 
|  |  | 
|  | if (strcmp("none", flag) == 0) { | 
|  | return new TimelineEventNopRecorder(); | 
|  | } | 
|  |  | 
|  | // Systrace recorder. | 
|  | if (strcmp("systrace", flag) == 0) { | 
|  | #if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID) | 
|  | return new TimelineEventSystraceRecorder(); | 
|  | #elif defined(DART_HOST_OS_MACOS) | 
|  | return new TimelineEventMacosRecorder(); | 
|  | #elif defined(DART_HOST_OS_FUCHSIA) | 
|  | return new TimelineEventFuchsiaRecorder(); | 
|  | #else | 
|  | // Not supported. A warning will be emitted below. | 
|  | #endif | 
|  | } | 
|  |  | 
|  | if (Utils::StrStartsWith(flag, "file") && | 
|  | (flag[4] == '\0' || flag[4] == ':' || flag[4] == '=')) { | 
|  | const char* filename = flag[4] == '\0' ? "dart-timeline.json" : &flag[5]; | 
|  | free(const_cast<char*>(FLAG_timeline_dir)); | 
|  | FLAG_timeline_dir = nullptr; | 
|  | return new TimelineEventFileRecorder(filename); | 
|  | } | 
|  |  | 
|  | if (strcmp("callback", flag) == 0) { | 
|  | return new TimelineEventEmbedderCallbackRecorder(); | 
|  | } | 
|  |  | 
|  | #if !defined(PRODUCT) | 
|  | #if defined(SUPPORT_PERFETTO) | 
|  | // The Perfetto file recorder is disabled in PRODUCT mode to avoid the large | 
|  | // binary size increase that it brings. | 
|  | { | 
|  | const intptr_t kPrefixLength = 12; | 
|  | if (Utils::StrStartsWith(flag, "perfettofile") && | 
|  | (flag[kPrefixLength] == '\0' || flag[kPrefixLength] == ':' || | 
|  | flag[kPrefixLength] == '=')) { | 
|  | const char* filename = flag[kPrefixLength] == '\0' | 
|  | ? "dart.perfetto-trace" | 
|  | : &flag[kPrefixLength + 1]; | 
|  | free(const_cast<char*>(FLAG_timeline_dir)); | 
|  | FLAG_timeline_dir = nullptr; | 
|  | return CreateTimelineEventPerfettoFileRecorder(filename); | 
|  | } | 
|  | } | 
|  | #endif  // defined(SUPPORT_PERFETTO) | 
|  |  | 
|  | // Recorders below do nothing useful in PRODUCT mode. You can't extract | 
|  | // information available in them without vm-service. | 
|  | if (strcmp("endless", flag) == 0) { | 
|  | return new TimelineEventEndlessRecorder(); | 
|  | } | 
|  |  | 
|  | if (strcmp("startup", flag) == 0) { | 
|  | return new TimelineEventStartupRecorder(); | 
|  | } | 
|  |  | 
|  | if (strcmp("ring", flag) == 0) { | 
|  | return new TimelineEventRingRecorder(); | 
|  | } | 
|  | #endif  // !defined(PRODUCT) | 
|  |  | 
|  | if (strlen(flag) > 0 && strcmp(flag, DEFAULT_TIMELINE_RECORDER) != 0) { | 
|  | OS::PrintErr( | 
|  | "Warning: requested %s timeline recorder which is not supported, " | 
|  | "defaulting to the " DEFAULT_TIMELINE_RECORDER " recorder\n", | 
|  | flag); | 
|  | } | 
|  |  | 
|  | return CreateDefaultTimelineRecorder(); | 
|  | } | 
|  |  | 
|  | // 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 == nullptr) { | 
|  | // Nothing set. | 
|  | return result; | 
|  | } | 
|  | char* save_ptr;  // Needed for strtok_r. | 
|  | // strtok modifies arg 1 so we make a copy of it. | 
|  | char* streams = Utils::StrDup(FLAG_timeline_streams); | 
|  | char* token = strtok_r(streams, ",", &save_ptr); | 
|  | while (token != nullptr) { | 
|  | result->Add(Utils::StrDup(token)); | 
|  | token = strtok_r(nullptr, ",", &save_ptr); | 
|  | } | 
|  | free(streams); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Frees the result of |GetEnabledByDefaultTimelineStreams|. | 
|  | static void FreeEnabledByDefaultTimelineStreams( | 
|  | MallocGrowableArray<char*>* streams) { | 
|  | if (streams == nullptr) { | 
|  | 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 != nullptr) || 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") != nullptr) || | 
|  | (strstr(checked_stream, stream) != nullptr)) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void Timeline::Init() { | 
|  | ASSERT(recorder_ == nullptr); | 
|  | recorder_ = CreateTimelineRecorder(); | 
|  |  | 
|  | RecorderSynchronizationLock::Init(); | 
|  |  | 
|  | // The following is needed to backfill information about any |OSThread|s that | 
|  | // were initialized before this point. | 
|  | OSThreadIterator it; | 
|  | while (it.HasNext()) { | 
|  | OSThread& thread = *it.Next(); | 
|  | recorder_->AddTrackMetadataBasedOnThread( | 
|  | OS::ProcessId(), OSThread::ThreadIdToIntPtr(thread.trace_id()), | 
|  | thread.name()); | 
|  | } | 
|  | if (FLAG_trace_timeline) { | 
|  | OS::PrintErr("Using the %s timeline recorder.\n", recorder_->name()); | 
|  | } | 
|  | ASSERT(recorder_ != nullptr); | 
|  | enabled_streams_ = GetEnabledByDefaultTimelineStreams(); | 
|  | // Global overrides. | 
|  | #define TIMELINE_STREAM_FLAG_DEFAULT(name, ...)                                \ | 
|  | stream_##name##_.set_enabled(HasStream(enabled_streams_, #name)); | 
|  | TIMELINE_STREAM_LIST(TIMELINE_STREAM_FLAG_DEFAULT) | 
|  | #undef TIMELINE_STREAM_FLAG_DEFAULT | 
|  | } | 
|  |  | 
|  | void Timeline::Cleanup() { | 
|  | ASSERT(recorder_ != nullptr); | 
|  |  | 
|  | #ifndef PRODUCT | 
|  | if (FLAG_timeline_dir != nullptr) { | 
|  | recorder_->WriteTo(FLAG_timeline_dir); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | // Disable global streams. | 
|  | #define TIMELINE_STREAM_DISABLE(name, ...)                                     \ | 
|  | Timeline::stream_##name##_.set_enabled(false); | 
|  | TIMELINE_STREAM_LIST(TIMELINE_STREAM_DISABLE) | 
|  | #undef TIMELINE_STREAM_DISABLE | 
|  | RecorderSynchronizationLock::WaitForShutdown(); | 
|  | Timeline::Clear(); | 
|  | delete recorder_; | 
|  | recorder_ = nullptr; | 
|  | if (enabled_streams_ != nullptr) { | 
|  | FreeEnabledByDefaultTimelineStreams(enabled_streams_); | 
|  | enabled_streams_ = nullptr; | 
|  | } | 
|  | } | 
|  |  | 
|  | void Timeline::ReclaimCachedBlocksFromThreads() { | 
|  | RecorderSynchronizationLockScope ls; | 
|  | TimelineEventRecorder* recorder = Timeline::recorder(); | 
|  | if (recorder == nullptr || ls.IsUninitialized()) { | 
|  | return; | 
|  | } | 
|  | ASSERT(recorder != nullptr); | 
|  | // 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->TimelineBlockLocked(); | 
|  | thread->SetTimelineBlockLocked(nullptr); | 
|  | recorder->FinishBlock(block); | 
|  | } | 
|  | } | 
|  |  | 
|  | #ifndef PRODUCT | 
|  | void Timeline::PrintFlagsToJSONArray(JSONArray* arr) { | 
|  | #define ADD_RECORDED_STREAM_NAME(name, ...)                                    \ | 
|  | if (stream_##name##_.enabled()) {                                            \ | 
|  | arr->AddValue(#name);                                                      \ | 
|  | } | 
|  | TIMELINE_STREAM_LIST(ADD_RECORDED_STREAM_NAME); | 
|  | #undef ADD_RECORDED_STREAM_NAME | 
|  | } | 
|  |  | 
|  | void Timeline::PrintFlagsToJSON(JSONStream* js) { | 
|  | JSONObject obj(js); | 
|  | obj.AddProperty("type", "TimelineFlags"); | 
|  | RecorderSynchronizationLockScope ls; | 
|  | TimelineEventRecorder* recorder = Timeline::recorder(); | 
|  | if (recorder == nullptr || !ls.IsActive()) { | 
|  | obj.AddProperty("recorderName", "null"); | 
|  | } else { | 
|  | obj.AddProperty("recorderName", recorder->name()); | 
|  | } | 
|  | { | 
|  | JSONArray availableStreams(&obj, "availableStreams"); | 
|  | #define ADD_STREAM_NAME(name, ...) availableStreams.AddValue(#name); | 
|  | TIMELINE_STREAM_LIST(ADD_STREAM_NAME); | 
|  | #undef ADD_STREAM_NAME | 
|  | } | 
|  | { | 
|  | JSONArray recordedStreams(&obj, "recordedStreams"); | 
|  | #define ADD_RECORDED_STREAM_NAME(name, ...)                                    \ | 
|  | if (stream_##name##_.enabled()) {                                            \ | 
|  | recordedStreams.AddValue(#name);                                           \ | 
|  | } | 
|  | TIMELINE_STREAM_LIST(ADD_RECORDED_STREAM_NAME); | 
|  | #undef ADD_RECORDED_STREAM_NAME | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | void Timeline::Clear() { | 
|  | RecorderSynchronizationLockScope ls; | 
|  | TimelineEventRecorder* recorder = Timeline::recorder(); | 
|  | if (recorder == nullptr || ls.IsUninitialized()) { | 
|  | return; | 
|  | } | 
|  | ASSERT(recorder != nullptr); | 
|  | // Acquire the recorder's lock to prevent the reclaimed blocks from being | 
|  | // handed out again until they have been cleared. | 
|  | MutexLocker ml(&recorder->lock_); | 
|  | ReclaimCachedBlocksFromThreads(); | 
|  | recorder->ClearLocked(); | 
|  | } | 
|  |  | 
|  | void TimelineEventArguments::SetNumArguments(intptr_t length) { | 
|  | if (length == length_) { | 
|  | return; | 
|  | } | 
|  | if (length == 0) { | 
|  | Free(); | 
|  | return; | 
|  | } | 
|  | if (buffer_ == nullptr) { | 
|  | // 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, Utils::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 measure_args; | 
|  | va_copy(measure_args, args); | 
|  | intptr_t len = Utils::VSNPrint(nullptr, 0, fmt, measure_args); | 
|  | va_end(measure_args); | 
|  |  | 
|  | char* buffer = reinterpret_cast<char*>(malloc(len + 1)); | 
|  | va_list print_args; | 
|  | va_copy(print_args, args); | 
|  | Utils::VSNPrint(buffer, (len + 1), fmt, print_args); | 
|  | va_end(print_args); | 
|  |  | 
|  | SetArgument(i, name, buffer); | 
|  | } | 
|  |  | 
|  | void TimelineEventArguments::StealArguments(TimelineEventArguments* arguments) { | 
|  | Free(); | 
|  | length_ = arguments->length_; | 
|  | buffer_ = arguments->buffer_; | 
|  | arguments->length_ = 0; | 
|  | arguments->buffer_ = nullptr; | 
|  | } | 
|  |  | 
|  | void TimelineEventArguments::Free() { | 
|  | if (buffer_ == nullptr) { | 
|  | return; | 
|  | } | 
|  | for (intptr_t i = 0; i < length_; i++) { | 
|  | free(buffer_[i].value); | 
|  | } | 
|  | free(buffer_); | 
|  | buffer_ = nullptr; | 
|  | length_ = 0; | 
|  | } | 
|  |  | 
|  | TimelineEventRecorder* Timeline::recorder_ = nullptr; | 
|  | Dart_TimelineRecorderCallback Timeline::callback_ = nullptr; | 
|  | MallocGrowableArray<char*>* Timeline::enabled_streams_ = nullptr; | 
|  | bool Timeline::recorder_discards_clock_values_ = false; | 
|  |  | 
|  | #define TIMELINE_STREAM_DEFINE(name, fuchsia_name, static_labels)              \ | 
|  | TimelineStream Timeline::stream_##name##_(#name, fuchsia_name,               \ | 
|  | static_labels, false); | 
|  | TIMELINE_STREAM_LIST(TIMELINE_STREAM_DEFINE) | 
|  | #undef TIMELINE_STREAM_DEFINE | 
|  |  | 
|  | TimelineEvent::TimelineEvent() | 
|  | : timestamp0_(0), | 
|  | timestamp1_or_id_(0), | 
|  | flow_id_count_(0), | 
|  | flow_ids_(), | 
|  | state_(0), | 
|  | label_(nullptr), | 
|  | stream_(nullptr), | 
|  | thread_(OSThread::kInvalidThreadId), | 
|  | isolate_id_(ILLEGAL_ISOLATE_ID), | 
|  | isolate_group_id_(ILLEGAL_ISOLATE_GROUP_ID) {} | 
|  |  | 
|  | TimelineEvent::~TimelineEvent() { | 
|  | Reset(); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::Reset() { | 
|  | timestamp0_ = 0; | 
|  | timestamp1_or_id_ = 0; | 
|  | flow_id_count_ = 0; | 
|  | flow_ids_.reset(); | 
|  | if (owns_label() && label_ != nullptr) { | 
|  | free(const_cast<char*>(label_)); | 
|  | } | 
|  | label_ = nullptr; | 
|  | stream_ = nullptr; | 
|  | thread_ = OSThread::kInvalidThreadId; | 
|  | isolate_id_ = ILLEGAL_ISOLATE_ID; | 
|  | isolate_group_id_ = ILLEGAL_ISOLATE_GROUP_ID; | 
|  | arguments_.Free(); | 
|  | state_ = 0; | 
|  | } | 
|  |  | 
|  | 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_or_id(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_or_id(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_or_id(async_id); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::DurationBegin(const char* label, int64_t micros) { | 
|  | Init(kDuration, label); | 
|  | set_timestamp0(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) { | 
|  | Init(kDuration, label); | 
|  | set_timestamp0(start_micros); | 
|  | set_timestamp1_or_id(end_micros); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::Begin(const char* label, int64_t id, int64_t micros) { | 
|  | Init(kBegin, label); | 
|  | set_timestamp0(micros); | 
|  | // Overload timestamp1_ with the event ID. This is required for the MacOS | 
|  | // recorder to work. | 
|  | set_timestamp1_or_id(id); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::End(const char* label, int64_t id, int64_t micros) { | 
|  | Init(kEnd, label); | 
|  | set_timestamp0(micros); | 
|  | // Overload timestamp1_ with the event ID. This is required for the MacOS | 
|  | // recorder to work. | 
|  | set_timestamp1_or_id(id); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::Counter(const char* label, int64_t micros) { | 
|  | Init(kCounter, label); | 
|  | set_timestamp0(micros); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::FlowBegin(const char* label, int64_t id, int64_t micros) { | 
|  | Init(kFlowBegin, label); | 
|  | set_timestamp0(micros); | 
|  | // Overload timestamp1_ with the flow ID. | 
|  | set_timestamp1_or_id(id); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::FlowStep(const char* label, int64_t id, int64_t micros) { | 
|  | Init(kFlowStep, label); | 
|  | set_timestamp0(micros); | 
|  | // Overload timestamp1_ with the flow ID. | 
|  | set_timestamp1_or_id(id); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::FlowEnd(const char* label, int64_t id, int64_t micros) { | 
|  | Init(kFlowEnd, label); | 
|  | set_timestamp0(micros); | 
|  | // Overload timestamp1_ with the flow ID. | 
|  | set_timestamp1_or_id(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); | 
|  | va_end(args); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::Complete() { | 
|  | TimelineEventRecorder* recorder = Timeline::recorder(); | 
|  | recorder->CompleteEvent(this); | 
|  | // Paired with |RecorderSynchronizationLock::EnterLock()| in | 
|  | // |TimelineStream::StartEvent()|. | 
|  | RecorderSynchronizationLock::ExitLock(); | 
|  | } | 
|  |  | 
|  | void TimelineEvent::Init(EventType event_type, const char* label) { | 
|  | ASSERT(label != nullptr); | 
|  | state_ = 0; | 
|  | timestamp0_ = 0; | 
|  | timestamp1_or_id_ = 0; | 
|  | flow_id_count_ = 0; | 
|  | flow_ids_.reset(); | 
|  | OSThread* os_thread = OSThread::Current(); | 
|  | ASSERT(os_thread != nullptr); | 
|  | thread_ = os_thread->trace_id(); | 
|  | auto thread = Thread::Current(); | 
|  | auto isolate = thread != nullptr ? thread->isolate() : nullptr; | 
|  | auto isolate_group = thread != nullptr ? thread->isolate_group() : nullptr; | 
|  | isolate_id_ = (isolate != nullptr) ? isolate->main_port() : ILLEGAL_PORT; | 
|  | isolate_group_id_ = (isolate_group != nullptr) ? isolate_group->id() : 0; | 
|  | isolate_data_ = | 
|  | (isolate != nullptr) ? isolate->init_callback_data() : nullptr; | 
|  | isolate_group_data_ = | 
|  | (isolate_group != nullptr) ? isolate_group->embedder_data() : nullptr; | 
|  | 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) const { | 
|  | 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); | 
|  | } | 
|  |  | 
|  | #ifndef PRODUCT | 
|  | void TimelineEvent::PrintJSON(JSONStream* stream) const { | 
|  | PrintJSON(stream->writer()); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | void TimelineEvent::PrintJSON(JSONWriter* writer) const { | 
|  | writer->OpenObject(); | 
|  | int64_t pid = OS::ProcessId(); | 
|  | int64_t tid = OSThread::ThreadIdToIntPtr(thread_); | 
|  | writer->PrintProperty("name", label_); | 
|  | writer->PrintProperty("cat", stream_ != nullptr ? stream_->name() : nullptr); | 
|  | writer->PrintProperty64("tid", tid); | 
|  | writer->PrintProperty64("pid", pid); | 
|  | writer->PrintProperty64("ts", TimeOrigin()); | 
|  | switch (event_type()) { | 
|  | case kBegin: { | 
|  | writer->PrintProperty("ph", "B"); | 
|  | } break; | 
|  | case kEnd: { | 
|  | writer->PrintProperty("ph", "E"); | 
|  | } break; | 
|  | case kDuration: { | 
|  | writer->PrintProperty("ph", "X"); | 
|  | writer->PrintProperty64("dur", TimeDuration()); | 
|  | } break; | 
|  | case kInstant: { | 
|  | writer->PrintProperty("ph", "i"); | 
|  | writer->PrintProperty("s", "p"); | 
|  | } break; | 
|  | case kAsyncBegin: { | 
|  | writer->PrintProperty("ph", "b"); | 
|  | writer->PrintfProperty("id", "%" Px64 "", Id()); | 
|  | } break; | 
|  | case kAsyncInstant: { | 
|  | writer->PrintProperty("ph", "n"); | 
|  | writer->PrintfProperty("id", "%" Px64 "", Id()); | 
|  | } break; | 
|  | case kAsyncEnd: { | 
|  | writer->PrintProperty("ph", "e"); | 
|  | writer->PrintfProperty("id", "%" Px64 "", Id()); | 
|  | } break; | 
|  | case kCounter: { | 
|  | writer->PrintProperty("ph", "C"); | 
|  | } break; | 
|  | case kFlowBegin: { | 
|  | writer->PrintProperty("ph", "s"); | 
|  | writer->PrintfProperty("id", "%" Px64 "", Id()); | 
|  | } break; | 
|  | case kFlowStep: { | 
|  | writer->PrintProperty("ph", "t"); | 
|  | writer->PrintfProperty("id", "%" Px64 "", Id()); | 
|  | } break; | 
|  | case kFlowEnd: { | 
|  | writer->PrintProperty("ph", "f"); | 
|  | writer->PrintProperty("bp", "e"); | 
|  | writer->PrintfProperty("id", "%" Px64 "", Id()); | 
|  | } break; | 
|  | case kMetadata: { | 
|  | writer->PrintProperty("ph", "M"); | 
|  | } break; | 
|  | default: | 
|  | UNIMPLEMENTED(); | 
|  | } | 
|  |  | 
|  | if (ArgsArePreSerialized()) { | 
|  | ASSERT(arguments_.length() == 1); | 
|  | writer->AppendSerializedObject("args", arguments_[0].value); | 
|  | if (HasIsolateId()) { | 
|  | writer->UncloseObject(); | 
|  | writer->PrintfProperty("isolateId", ISOLATE_SERVICE_ID_FORMAT_STRING, | 
|  | static_cast<int64_t>(isolate_id_)); | 
|  | writer->CloseObject(); | 
|  | } | 
|  | if (HasIsolateGroupId()) { | 
|  | writer->UncloseObject(); | 
|  | writer->PrintfProperty("isolateGroupId", | 
|  | ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING, | 
|  | isolate_group_id_); | 
|  | writer->CloseObject(); | 
|  | } else { | 
|  | ASSERT(isolate_group_id_ == ILLEGAL_PORT); | 
|  | } | 
|  | } else { | 
|  | writer->OpenObject("args"); | 
|  | for (intptr_t i = 0; i < arguments_.length(); i++) { | 
|  | const TimelineEventArgument& arg = arguments_[i]; | 
|  | writer->PrintProperty(arg.name, arg.value); | 
|  | } | 
|  | if (HasIsolateId()) { | 
|  | writer->PrintfProperty("isolateId", ISOLATE_SERVICE_ID_FORMAT_STRING, | 
|  | static_cast<int64_t>(isolate_id_)); | 
|  | } | 
|  | if (HasIsolateGroupId()) { | 
|  | writer->PrintfProperty("isolateGroupId", | 
|  | ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING, | 
|  | isolate_group_id_); | 
|  | } else { | 
|  | ASSERT(isolate_group_id_ == ILLEGAL_PORT); | 
|  | } | 
|  | writer->CloseObject(); | 
|  | } | 
|  | writer->CloseObject(); | 
|  | } | 
|  |  | 
|  | #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  | namespace { | 
|  |  | 
|  | // Trait used to map 64-bit ids (e.g. isolate or isolate group id) to | 
|  | // interned id of a corresponding string representation. | 
|  | // | 
|  | // This way we only need to generate formatted string once, instead of | 
|  | // repeatedly formatting it and then interning resulting string to get an | 
|  | // iid. | 
|  | class IdToIidTrait { | 
|  | public: | 
|  | struct Pair { | 
|  | uint64_t id; | 
|  | uint64_t formatted_iid; | 
|  | }; | 
|  | using Key = uint64_t; | 
|  | using Value = uint64_t; | 
|  |  | 
|  | static Key KeyOf(const Pair& kv) { return kv.id; } | 
|  | static Value ValueOf(const Pair& kv) { return kv.formatted_iid; } | 
|  | static uword Hash(Key key) { | 
|  | return Utils::WordHash(static_cast<intptr_t>(key)); | 
|  | } | 
|  | static bool IsKeyEqual(const Pair& kv, Key key) { return kv.id == key; } | 
|  | }; | 
|  |  | 
|  | using IdToIidMap = MallocDirectChainedHashMap<IdToIidTrait>; | 
|  |  | 
|  | class InternedDataBuilder : public ValueObject { | 
|  | private: | 
|  | using SequenceFlags = perfetto::protos::pbzero::TracePacket_SequenceFlags; | 
|  |  | 
|  | public: | 
|  | // InternedData contains multiple independent interning dictionaries which | 
|  | // are used for different attributes. | 
|  | #define PERFETTO_INTERNED_STRINGS_FIELDS_LIST(V)                               \ | 
|  | V(event_categories, name)                                                    \ | 
|  | V(event_names, name)                                                         \ | 
|  | V(debug_annotation_names, name)                                              \ | 
|  | V(debug_annotation_string_values, str) | 
|  |  | 
|  | // Direct access for known strings. | 
|  | #define PERFETTO_COMMON_INTERNED_STRINGS_LIST(V)                               \ | 
|  | V(debug_annotation_names, isolateId)                                         \ | 
|  | V(debug_annotation_names, isolateGroupId) | 
|  |  | 
|  | InternedDataBuilder() = default; | 
|  |  | 
|  | // Emit all strings added since the last invocation of |AttachInternedDataTo| | 
|  | // into |interned_data| of the given |TracePacket|. | 
|  | // | 
|  | // Mark the packet as depending on incremental state. | 
|  | void AttachInternedDataTo(perfetto::protos::pbzero::TracePacket* packet) { | 
|  | if (!AnyInternerHasNewlyInternedEntries()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | packet->set_sequence_flags(sequence_flags_); | 
|  | // The first packet will have SEQ_INCREMENTAL_STATE_CLEARED | 
|  | // the rest will just have SEQ_NEEDS_INCREMENTAL_STATE. | 
|  | sequence_flags_ &= ~SequenceFlags::SEQ_INCREMENTAL_STATE_CLEARED; | 
|  |  | 
|  | auto interned_data = packet->set_interned_data(); | 
|  |  | 
|  | // Flush individual interning dictionaries. | 
|  | #define FLUSH_FIELD(name, proto_field)                                         \ | 
|  | name##_.FlushNewlyInternedTo([interned_data](auto& iid, auto& str) {         \ | 
|  | auto entry = interned_data->add_##name();                                  \ | 
|  | entry->set_iid(iid);                                                       \ | 
|  | entry->set_##proto_field(str);                                             \ | 
|  | }); | 
|  |  | 
|  | PERFETTO_INTERNED_STRINGS_FIELDS_LIST(FLUSH_FIELD) | 
|  | #undef FLUSH_FIELD | 
|  | } | 
|  |  | 
|  | #define DEFINE_GETTER(name, proto_field)                                       \ | 
|  | perfetto_utils::StringInterner<Malloc>& name() { return name##_; } | 
|  | PERFETTO_INTERNED_STRINGS_FIELDS_LIST(DEFINE_GETTER) | 
|  | #undef DEFINE_GETTER | 
|  |  | 
|  | #define DEFINE_GETTER_FOR_COMMON_STRING(category, str)                         \ | 
|  | uint64_t iid_##str() {                                                       \ | 
|  | if (iid_##str##_ == 0) {                                                   \ | 
|  | iid_##str##_ = category().Intern(#str);                                  \ | 
|  | }                                                                          \ | 
|  | return iid_##str##_;                                                       \ | 
|  | } | 
|  |  | 
|  | PERFETTO_COMMON_INTERNED_STRINGS_LIST(DEFINE_GETTER_FOR_COMMON_STRING) | 
|  |  | 
|  | #undef DEFINE_GETTER_FOR_COMMON_STRING | 
|  |  | 
|  | uint64_t InternFormattedIsolateId(uint64_t isolate_id) { | 
|  | return InternFormattedIdForDebugAnnotation( | 
|  | isolate_id_to_iid_of_formatted_string_, | 
|  | ISOLATE_SERVICE_ID_FORMAT_STRING, isolate_id); | 
|  | } | 
|  |  | 
|  | uint64_t InternFormattedIsolateGroupId(uint64_t isolate_group_id) { | 
|  | return InternFormattedIdForDebugAnnotation( | 
|  | isolate_group_id_to_iid_of_formatted_string_, | 
|  | ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING, isolate_group_id); | 
|  | } | 
|  |  | 
|  | private: | 
|  | template <std::size_t kFormatLen> | 
|  | uint64_t InternFormattedIdForDebugAnnotation(IdToIidMap& cache, | 
|  | const char (&format)[kFormatLen], | 
|  | uint64_t id) { | 
|  | if (auto iid = cache.Lookup(id)) { | 
|  | return iid->formatted_iid; | 
|  | } | 
|  |  | 
|  | // 20 characters is enough to format any uint64_t (or int64_t) value. | 
|  | char formatted[kFormatLen + 20]; | 
|  | Utils::SNPrint(formatted, ARRAY_SIZE(formatted), format, id); | 
|  |  | 
|  | auto formatted_iid = debug_annotation_string_values().Intern(formatted); | 
|  | cache.Insert({id, formatted_iid}); | 
|  | return formatted_iid; | 
|  | } | 
|  |  | 
|  | bool AnyInternerHasNewlyInternedEntries() const { | 
|  | #define CHECK_FIELD(name, proto_field)                                         \ | 
|  | if (name##_.HasNewlyInternedEntries()) return true; | 
|  |  | 
|  | PERFETTO_INTERNED_STRINGS_FIELDS_LIST(CHECK_FIELD) | 
|  | #undef CHECK_FIELD | 
|  | return false; | 
|  | } | 
|  |  | 
|  | uint32_t sequence_flags_ = SequenceFlags::SEQ_INCREMENTAL_STATE_CLEARED | | 
|  | SequenceFlags::SEQ_NEEDS_INCREMENTAL_STATE; | 
|  |  | 
|  | // These are interned in debug_annotation_string_values space. | 
|  | IdToIidMap isolate_id_to_iid_of_formatted_string_; | 
|  | IdToIidMap isolate_group_id_to_iid_of_formatted_string_; | 
|  |  | 
|  | #define DEFINE_FIELD_FOR_COMMON_STRING(category, str) uint64_t iid_##str##_ = 0; | 
|  |  | 
|  | PERFETTO_COMMON_INTERNED_STRINGS_LIST(DEFINE_FIELD_FOR_COMMON_STRING) | 
|  |  | 
|  | #undef DEFINE_FIELD_FOR_COMMON_STRING | 
|  |  | 
|  | #define DEFINE_FIELD(name, proto_field)                                        \ | 
|  | perfetto_utils::StringInterner<Malloc> name##_; | 
|  | PERFETTO_INTERNED_STRINGS_FIELDS_LIST(DEFINE_FIELD) | 
|  | #undef DEFINE_FIELD | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(InternedDataBuilder); | 
|  | }; | 
|  |  | 
|  | class TracePacketWriter : public ValueObject { | 
|  | public: | 
|  | using TracePacket = perfetto::protos::pbzero::TracePacket; | 
|  | using TrackEvent = perfetto::protos::pbzero::TrackEvent; | 
|  |  | 
|  | using WriteCallback = | 
|  | std::function<void(protozero::HeapBuffered<TracePacket>&)>; | 
|  |  | 
|  | TracePacketWriter(protozero::HeapBuffered<TracePacket>& packet, | 
|  | WriteCallback&& write_callback, | 
|  | bool intern_strings) | 
|  | : packet_(packet), | 
|  | write_callback_(std::move(write_callback)), | 
|  | intern_strings_(intern_strings) {} | 
|  |  | 
|  | // Converting contents of the given |TimelineEvent| into one or more | 
|  | // Perfetto packets and write them out using |write_callback_| which | 
|  | // was specified when this writer was constructed. | 
|  | // | 
|  | // It uses scratch |packet_| which is reset after it is written out. | 
|  | void WriteEvent(const TimelineEvent& event) { | 
|  | if (!CanBeRepresented(event.event_type())) { | 
|  | return; | 
|  | } | 
|  | if (event.IsDuration()) { | 
|  | // Duration events must be converted to pairs of begin and end events to | 
|  | // be serialized in Perfetto's format. | 
|  | PopulateAndWritePacket(TimelineEvent::kBegin, event.TimeOrigin(), event); | 
|  | PopulateAndWritePacket(TimelineEvent::kEnd, event.TimeEnd(), event); | 
|  | } else { | 
|  | PopulateAndWritePacket(event.event_type(), event.TimeOrigin(), event); | 
|  | } | 
|  | } | 
|  |  | 
|  | private: | 
|  | static TrackEvent::Type ToPerfettoType(TimelineEvent::EventType event_type) { | 
|  | switch (event_type) { | 
|  | case TimelineEvent::kAsyncBegin: | 
|  | case TimelineEvent::kBegin: | 
|  | return TrackEvent::Type::TYPE_SLICE_BEGIN; | 
|  | case TimelineEvent::kAsyncInstant: | 
|  | case TimelineEvent::kInstant: | 
|  | return TrackEvent::Type::TYPE_INSTANT; | 
|  | case TimelineEvent::kAsyncEnd: | 
|  | case TimelineEvent::kEnd: | 
|  | return TrackEvent::Type::TYPE_SLICE_END; | 
|  | default: | 
|  | return TrackEvent::Type::TYPE_UNSPECIFIED; | 
|  | } | 
|  | } | 
|  |  | 
|  | static bool IsSync(TimelineEvent::EventType event_type) { | 
|  | switch (event_type) { | 
|  | case TimelineEvent::kBegin: | 
|  | case TimelineEvent::kInstant: | 
|  | case TimelineEvent::kEnd: | 
|  | return true; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | static bool CanBeRepresented(TimelineEvent::EventType event_type) { | 
|  | return event_type == TimelineEvent::kDuration || | 
|  | ToPerfettoType(event_type) != TrackEvent::Type::TYPE_UNSPECIFIED; | 
|  | } | 
|  |  | 
|  | void PopulateAndWritePacket(TimelineEvent::EventType event_type, | 
|  | int64_t timestamp, | 
|  | const TimelineEvent& event) { | 
|  | PopulatePacket(event_type, timestamp, event); | 
|  | interned_data_builder_.AttachInternedDataTo(packet_.get()); | 
|  | write_callback_(packet_); | 
|  | packet_.Reset(); | 
|  | } | 
|  |  | 
|  | void PopulatePacket(TimelineEvent::EventType event_type, | 
|  | int64_t timestamp, | 
|  | const TimelineEvent& event) { | 
|  | ASSERT(event_type == event.event_type() || | 
|  | (event.IsDuration() && (event_type == TimelineEvent::kBegin || | 
|  | event_type == TimelineEvent::kEnd))); | 
|  | perfetto_utils::SetTrustedPacketSequenceId(packet_.get()); | 
|  | perfetto_utils::SetTimestampAndMonotonicClockId(packet_.get(), timestamp); | 
|  |  | 
|  | TrackEvent* track_event = packet_->set_track_event(); | 
|  | SetTrackEventCategory(track_event, event.stream()->name()); | 
|  |  | 
|  | track_event->set_track_uuid(IsSync(event_type) | 
|  | ? OSThread::ThreadIdToIntPtr(event.thread()) | 
|  | : event.Id()); | 
|  | const auto perfetto_type = ToPerfettoType(event_type); | 
|  | track_event->set_type(perfetto_type); | 
|  | if (perfetto_type != TrackEvent::Type::TYPE_SLICE_END) { | 
|  | SetTrackEventName(track_event, event.label()); | 
|  | for (intptr_t i = 0; i < event.flow_id_count(); ++i) { | 
|  | // TODO(derekx): |TrackEvent|s have a |terminating_flow_ids| field that | 
|  | // we aren't able to populate right now because we aren't keeping track | 
|  | // of terminating flow IDs in |TimelineEvent|. I'm not even sure if | 
|  | // using that field will provide any benefit though. | 
|  | track_event->add_flow_ids(event.FlowIds()[i]); | 
|  | } | 
|  | } | 
|  | AddDebugAnnotations(track_event, event); | 
|  | } | 
|  |  | 
|  | void AddDebugAnnotations(TrackEvent* track_event, | 
|  | const TimelineEvent& event) { | 
|  | if (event.GetNumArguments() > 0) { | 
|  | if (event.ArgsArePreSerialized()) { | 
|  | ASSERT(event.GetNumArguments() == 1); | 
|  | perfetto::protos::pbzero::DebugAnnotation& debug_annotation = | 
|  | *track_event->add_debug_annotations(); | 
|  | SetDebugAnnotationName(debug_annotation, event.arguments()[0].name); | 
|  | debug_annotation.set_legacy_json_value(event.arguments()[0].value); | 
|  | } else { | 
|  | for (intptr_t i = 0; i < event.GetNumArguments(); ++i) { | 
|  | perfetto::protos::pbzero::DebugAnnotation& debug_annotation = | 
|  | *track_event->add_debug_annotations(); | 
|  | SetDebugAnnotationName(debug_annotation, event.arguments()[i].name); | 
|  | SetDebugAnnotationStringValue(debug_annotation, | 
|  | event.arguments()[i].value); | 
|  | } | 
|  | } | 
|  | } | 
|  | if (event.HasIsolateId()) { | 
|  | perfetto::protos::pbzero::DebugAnnotation& debug_annotation = | 
|  | *track_event->add_debug_annotations(); | 
|  | SetDebugAnnotationName(debug_annotation, "isolateId", [this](auto name) { | 
|  | return interned_data_builder_.iid_isolateId(); | 
|  | }); | 
|  | SetDebugAnnotationStringValueFromFormattedId( | 
|  | debug_annotation, ISOLATE_SERVICE_ID_FORMAT_STRING, | 
|  | event.isolate_id(), [this](auto id) { | 
|  | return interned_data_builder_.InternFormattedIsolateId(id); | 
|  | }); | 
|  | } | 
|  | if (event.HasIsolateGroupId()) { | 
|  | perfetto::protos::pbzero::DebugAnnotation& debug_annotation = | 
|  | *track_event->add_debug_annotations(); | 
|  | SetDebugAnnotationName( | 
|  | debug_annotation, "isolateGroupId", [this](auto name) { | 
|  | return interned_data_builder_.iid_isolateGroupId(); | 
|  | }); | 
|  | SetDebugAnnotationStringValueFromFormattedId( | 
|  | debug_annotation, ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING, | 
|  | event.isolate_group_id(), [this](auto id) { | 
|  | return interned_data_builder_.InternFormattedIsolateGroupId(id); | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Helpers for setting string valued properties on |TrackEvent| and | 
|  | // |DebugAnnotation|, these can use interning if |intern_strings_| is | 
|  | // |true|. | 
|  |  | 
|  | void SetTrackEventCategory(TrackEvent* track_event, const char* value) { | 
|  | if (intern_strings_) { | 
|  | track_event->add_category_iids( | 
|  | interned_data_builder_.event_categories().Intern(value)); | 
|  | } else { | 
|  | track_event->add_categories(value); | 
|  | } | 
|  | } | 
|  |  | 
|  | void SetTrackEventName(TrackEvent* track_event, const char* value) { | 
|  | if (intern_strings_) { | 
|  | track_event->set_name_iid( | 
|  | interned_data_builder_.event_names().Intern(value)); | 
|  | } else { | 
|  | track_event->set_name(value); | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename F> | 
|  | void SetDebugAnnotationName( | 
|  | perfetto::protos::pbzero::DebugAnnotation& debug_annotation, | 
|  | const char* name, | 
|  | F&& intern) { | 
|  | if (intern_strings_) { | 
|  | debug_annotation.set_name_iid(intern(name)); | 
|  | } else { | 
|  | debug_annotation.set_name(name); | 
|  | } | 
|  | } | 
|  |  | 
|  | void SetDebugAnnotationName( | 
|  | perfetto::protos::pbzero::DebugAnnotation& debug_annotation, | 
|  | const char* name) { | 
|  | SetDebugAnnotationName(debug_annotation, name, [this](auto name) { | 
|  | return interned_data_builder_.debug_annotation_names().Intern(name); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void SetDebugAnnotationStringValue( | 
|  | perfetto::protos::pbzero::DebugAnnotation& debug_annotation, | 
|  | const char* value) { | 
|  | if (intern_strings_) { | 
|  | debug_annotation.set_string_value_iid( | 
|  | interned_data_builder_.debug_annotation_string_values().Intern( | 
|  | value)); | 
|  | } else { | 
|  | debug_annotation.set_string_value(value); | 
|  | } | 
|  | } | 
|  |  | 
|  | template <std::size_t kFormatLen, typename F> | 
|  | void SetDebugAnnotationStringValueFromFormattedId( | 
|  | perfetto::protos::pbzero::DebugAnnotation& debug_annotation, | 
|  | const char (&format)[kFormatLen], | 
|  | uint64_t id, | 
|  | F&& intern_id) { | 
|  | if (intern_strings_) { | 
|  | debug_annotation.set_string_value_iid(intern_id(id)); | 
|  | } else { | 
|  | // 20 characters is enough to format any uint64_t (or int64_t) value. | 
|  | char formatted[kFormatLen + 20]; | 
|  | Utils::SNPrint(formatted, ARRAY_SIZE(formatted), format, id); | 
|  | debug_annotation.set_string_value(formatted); | 
|  | } | 
|  | } | 
|  |  | 
|  | protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>& packet_; | 
|  | WriteCallback write_callback_; | 
|  | const bool intern_strings_; | 
|  |  | 
|  | InternedDataBuilder interned_data_builder_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(TracePacketWriter); | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  | #endif  // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  |  | 
|  | int64_t TimelineEvent::LowTime() const { | 
|  | return timestamp0_; | 
|  | } | 
|  |  | 
|  | int64_t TimelineEvent::HighTime() const { | 
|  | if (event_type() == kDuration) { | 
|  | return timestamp1_or_id_; | 
|  | } else { | 
|  | return timestamp0_; | 
|  | } | 
|  | } | 
|  |  | 
|  | int64_t TimelineEvent::TimeDuration() const { | 
|  | ASSERT(event_type() == kDuration); | 
|  | if (timestamp1_or_id_ == 0) { | 
|  | // This duration is still open, use current time as end. | 
|  | return OS::GetCurrentMonotonicMicrosForTimeline() - timestamp0_; | 
|  | } | 
|  | return timestamp1_or_id_ - timestamp0_; | 
|  | } | 
|  |  | 
|  | bool TimelineEvent::HasIsolateId() const { | 
|  | return isolate_id_ != ILLEGAL_ISOLATE_ID; | 
|  | } | 
|  |  | 
|  | bool TimelineEvent::HasIsolateGroupId() const { | 
|  | return isolate_group_id_ != ILLEGAL_ISOLATE_GROUP_ID; | 
|  | } | 
|  |  | 
|  | void TimelineEvent::ClearIsolateGroupId() { | 
|  | isolate_group_id_ = ILLEGAL_ISOLATE_GROUP_ID; | 
|  | } | 
|  |  | 
|  | TimelineTrackMetadata::TimelineTrackMetadata(intptr_t pid, | 
|  | intptr_t tid, | 
|  | CStringUniquePtr&& track_name) | 
|  | : pid_(pid), tid_(tid), track_name_(std::move(track_name)) {} | 
|  |  | 
|  | void TimelineTrackMetadata::set_track_name(CStringUniquePtr&& track_name) { | 
|  | track_name_ = std::move(track_name); | 
|  | } | 
|  |  | 
|  | #if !defined(PRODUCT) | 
|  | void TimelineTrackMetadata::PrintJSON(const JSONArray& jsarr_events) const { | 
|  | JSONObject jsobj(&jsarr_events); | 
|  | jsobj.AddProperty("name", "thread_name"); | 
|  | jsobj.AddProperty("ph", "M"); | 
|  | jsobj.AddProperty("pid", pid()); | 
|  | jsobj.AddProperty("tid", tid()); | 
|  | { | 
|  | JSONObject jsobj_args(&jsobj, "args"); | 
|  | jsobj_args.AddPropertyF("name", "%s (%" Pd ")", track_name(), tid()); | 
|  | jsobj_args.AddProperty("mode", "basic"); | 
|  | } | 
|  | } | 
|  |  | 
|  | #if defined(SUPPORT_PERFETTO) | 
|  | void TimelineTrackMetadata::PopulateTracePacket( | 
|  | perfetto::protos::pbzero::TracePacket* track_descriptor_packet) const { | 
|  | perfetto_utils::SetTrustedPacketSequenceId(track_descriptor_packet); | 
|  |  | 
|  | perfetto::protos::pbzero::TrackDescriptor& track_descriptor = | 
|  | *track_descriptor_packet->set_track_descriptor(); | 
|  | track_descriptor.set_parent_uuid(pid()); | 
|  | track_descriptor.set_uuid(tid()); | 
|  |  | 
|  | perfetto::protos::pbzero::ThreadDescriptor& thread_descriptor = | 
|  | *track_descriptor.set_thread(); | 
|  | thread_descriptor.set_pid(pid()); | 
|  | thread_descriptor.set_tid(tid()); | 
|  | thread_descriptor.set_thread_name(track_name()); | 
|  | } | 
|  | #endif  // defined(SUPPORT_PERFETTO) | 
|  | #endif  // !defined(PRODUCT) | 
|  |  | 
|  | AsyncTimelineTrackMetadata::AsyncTimelineTrackMetadata(intptr_t pid, | 
|  | intptr_t async_id) | 
|  | : pid_(pid), async_id_(async_id) {} | 
|  |  | 
|  | #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  | void AsyncTimelineTrackMetadata::PopulateTracePacket( | 
|  | perfetto::protos::pbzero::TracePacket* track_descriptor_packet) const { | 
|  | perfetto_utils::SetTrustedPacketSequenceId(track_descriptor_packet); | 
|  | perfetto::protos::pbzero::TrackDescriptor& track_descriptor = | 
|  | *track_descriptor_packet->set_track_descriptor(); | 
|  | track_descriptor.set_parent_uuid(pid()); | 
|  | track_descriptor.set_uuid(async_id()); | 
|  | } | 
|  | #endif  // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  |  | 
|  | TimelineStream::TimelineStream(const char* name, | 
|  | const char* fuchsia_name, | 
|  | bool has_static_labels, | 
|  | bool enabled) | 
|  | : name_(name), | 
|  | fuchsia_name_(fuchsia_name), | 
|  | #if defined(DART_HOST_OS_FUCHSIA) | 
|  | enabled_(static_cast<uintptr_t>(true))  // For generated code. | 
|  | #else | 
|  | enabled_(static_cast<uintptr_t>(enabled)) | 
|  | #endif | 
|  | { | 
|  | #if defined(DART_HOST_OS_MACOS) | 
|  | macos_log_ = os_log_create("Dart", name); | 
|  | has_static_labels_ = has_static_labels; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | TimelineEvent* TimelineStream::StartEvent() { | 
|  | // Paired with |RecorderSynchronizationLock::ExitLock()| in | 
|  | // |TimelineEvent::Complete()|. | 
|  | // | 
|  | // The lock must be held until the event is completed to avoid having the | 
|  | // memory backing the event being freed in the middle of processing the | 
|  | // event. | 
|  | RecorderSynchronizationLock::EnterLock(); | 
|  | TimelineEventRecorder* recorder = Timeline::recorder(); | 
|  | if (!enabled() || (recorder == nullptr) || | 
|  | !RecorderSynchronizationLock::IsActive()) { | 
|  | RecorderSynchronizationLock::ExitLock(); | 
|  | return nullptr; | 
|  | } | 
|  | ASSERT(name_ != nullptr); | 
|  | TimelineEvent* event = recorder->StartEvent(); | 
|  | if (event == nullptr) { | 
|  | RecorderSynchronizationLock::ExitLock(); | 
|  | return nullptr; | 
|  | } | 
|  | event->StreamInit(this); | 
|  | return event; | 
|  | } | 
|  |  | 
|  | TimelineEventScope::TimelineEventScope(TimelineStream* stream, | 
|  | const char* label) | 
|  | : StackResource(static_cast<Thread*>(nullptr)), | 
|  | 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_ != nullptr); | 
|  | ASSERT(stream_ != nullptr); | 
|  | if (!stream_->enabled()) { | 
|  | // Stream is not enabled, do nothing. | 
|  | return; | 
|  | } | 
|  | enabled_ = true; | 
|  | Thread* thread = static_cast<Thread*>(this->thread()); | 
|  | if (thread != nullptr) { | 
|  | id_ = thread->GetNextTaskId(); | 
|  | } else { | 
|  | static RelaxedAtomic<int64_t> next_bootstrap_task_id = {0}; | 
|  | id_ = next_bootstrap_task_id.fetch_add(1); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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); | 
|  | va_end(args); | 
|  | } | 
|  |  | 
|  | void TimelineEventScope::StealArguments(TimelineEvent* event) { | 
|  | if (event == nullptr) { | 
|  | return; | 
|  | } | 
|  | event->StealArguments(&arguments_); | 
|  | } | 
|  |  | 
|  | TimelineBeginEndScope::TimelineBeginEndScope(TimelineStream* stream, | 
|  | const char* label) | 
|  | : TimelineEventScope(stream, label) { | 
|  | EmitBegin(); | 
|  | } | 
|  |  | 
|  | TimelineBeginEndScope::TimelineBeginEndScope(Thread* thread, | 
|  | TimelineStream* stream, | 
|  | const char* label) | 
|  | : TimelineEventScope(thread, stream, label) { | 
|  | EmitBegin(); | 
|  | } | 
|  |  | 
|  | TimelineBeginEndScope::~TimelineBeginEndScope() { | 
|  | EmitEnd(); | 
|  | } | 
|  |  | 
|  | void TimelineBeginEndScope::EmitBegin() { | 
|  | if (!ShouldEmitEvent()) { | 
|  | return; | 
|  | } | 
|  | TimelineEvent* event = stream()->StartEvent(); | 
|  | if (event == nullptr) { | 
|  | // Stream is now disabled. | 
|  | set_enabled(false); | 
|  | return; | 
|  | } | 
|  | ASSERT(event != nullptr); | 
|  | // Emit a begin event. | 
|  | event->Begin(label(), id()); | 
|  | event->Complete(); | 
|  | } | 
|  |  | 
|  | void TimelineBeginEndScope::EmitEnd() { | 
|  | if (!ShouldEmitEvent()) { | 
|  | return; | 
|  | } | 
|  | TimelineEvent* event = stream()->StartEvent(); | 
|  | if (event == nullptr) { | 
|  | // Stream is now disabled. | 
|  | set_enabled(false); | 
|  | return; | 
|  | } | 
|  | ASSERT(event != nullptr); | 
|  | // Emit an end event. | 
|  | event->End(label(), id()); | 
|  | StealArguments(event); | 
|  | event->Complete(); | 
|  | } | 
|  |  | 
|  | bool TimelineEventBlock::InUseLocked() const { | 
|  | ASSERT(Timeline::recorder()->lock_.IsOwnedByCurrentThread()); | 
|  | return in_use_; | 
|  | } | 
|  |  | 
|  | bool TimelineEventBlock::ContainsEventsThatCanBeSerializedLocked() const { | 
|  | ASSERT(Timeline::recorder()->lock_.IsOwnedByCurrentThread()); | 
|  | // Check that the block is not in use and not empty. |!block->in_use()| must | 
|  | // be checked first because we are only holding |lock_|. Holding |lock_| | 
|  | // makes it safe to call |in_use()| on any block, but only makes it safe to | 
|  | // call |IsEmpty()| on blocks that are not in use. | 
|  | return !InUseLocked() && !IsEmpty(); | 
|  | } | 
|  |  | 
|  | 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() | 
|  | : time_low_micros_(0), | 
|  | time_high_micros_(0), | 
|  | track_uuid_to_track_metadata_lock_(), | 
|  | track_uuid_to_track_metadata_( | 
|  | &SimpleHashMap::SamePointerValue, | 
|  | TimelineEventRecorder::kTrackUuidToTrackMetadataInitialCapacity), | 
|  | async_track_uuid_to_track_metadata_lock_(), | 
|  | async_track_uuid_to_track_metadata_( | 
|  | &SimpleHashMap::SamePointerValue, | 
|  | TimelineEventRecorder::kTrackUuidToTrackMetadataInitialCapacity) {} | 
|  |  | 
|  | TimelineEventRecorder::~TimelineEventRecorder() { | 
|  | // We do not need to lock the following section, because at this point | 
|  | // |RecorderSynchronizationLock| must have been put in a state that prevents | 
|  | // the metadata maps from being modified. | 
|  | for (SimpleHashMap::Entry* entry = track_uuid_to_track_metadata_.Start(); | 
|  | entry != nullptr; entry = track_uuid_to_track_metadata_.Next(entry)) { | 
|  | TimelineTrackMetadata* value = | 
|  | static_cast<TimelineTrackMetadata*>(entry->value); | 
|  | delete value; | 
|  | } | 
|  | for (SimpleHashMap::Entry* entry = | 
|  | async_track_uuid_to_track_metadata_.Start(); | 
|  | entry != nullptr; | 
|  | entry = async_track_uuid_to_track_metadata_.Next(entry)) { | 
|  | AsyncTimelineTrackMetadata* value = | 
|  | static_cast<AsyncTimelineTrackMetadata*>(entry->value); | 
|  | delete value; | 
|  | } | 
|  | } | 
|  |  | 
|  | #ifndef PRODUCT | 
|  | void TimelineEventRecorder::PrintJSONMeta(const JSONArray& jsarr_events) { | 
|  | MutexLocker ml(&track_uuid_to_track_metadata_lock_); | 
|  | for (SimpleHashMap::Entry* entry = track_uuid_to_track_metadata_.Start(); | 
|  | entry != nullptr; entry = track_uuid_to_track_metadata_.Next(entry)) { | 
|  | TimelineTrackMetadata* value = | 
|  | static_cast<TimelineTrackMetadata*>(entry->value); | 
|  | value->PrintJSON(jsarr_events); | 
|  | } | 
|  | } | 
|  |  | 
|  | #if defined(SUPPORT_PERFETTO) | 
|  | void TimelineEventRecorder::PrintPerfettoMeta( | 
|  | JSONBase64String* jsonBase64String) { | 
|  | ASSERT(jsonBase64String != nullptr); | 
|  |  | 
|  | perfetto_utils::PopulateClockSnapshotPacket(packet_.get()); | 
|  | perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, &packet_); | 
|  | packet_.Reset(); | 
|  | perfetto_utils::PopulateProcessDescriptorPacket(packet_.get()); | 
|  | perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, &packet_); | 
|  | packet_.Reset(); | 
|  |  | 
|  | { | 
|  | MutexLocker ml(&async_track_uuid_to_track_metadata_lock_); | 
|  | for (SimpleHashMap::Entry* entry = | 
|  | async_track_uuid_to_track_metadata_.Start(); | 
|  | entry != nullptr; | 
|  | entry = async_track_uuid_to_track_metadata_.Next(entry)) { | 
|  | AsyncTimelineTrackMetadata* value = | 
|  | static_cast<AsyncTimelineTrackMetadata*>(entry->value); | 
|  | value->PopulateTracePacket(packet_.get()); | 
|  | perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, | 
|  | &packet_); | 
|  | packet_.Reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | { | 
|  | MutexLocker ml(&track_uuid_to_track_metadata_lock_); | 
|  | for (SimpleHashMap::Entry* entry = track_uuid_to_track_metadata_.Start(); | 
|  | entry != nullptr; entry = track_uuid_to_track_metadata_.Next(entry)) { | 
|  | TimelineTrackMetadata* value = | 
|  | static_cast<TimelineTrackMetadata*>(entry->value); | 
|  | value->PopulateTracePacket(packet_.get()); | 
|  | perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, | 
|  | &packet_); | 
|  | packet_.Reset(); | 
|  | } | 
|  | } | 
|  | } | 
|  | #endif  // defined(SUPPORT_PERFETTO) | 
|  | #endif  // !defined(PRODUCT) | 
|  |  | 
|  | TimelineEvent* TimelineEventRecorder::ThreadBlockStartEvent() { | 
|  | // Grab the current thread. | 
|  | OSThread* thread = OSThread::Current(); | 
|  | ASSERT(thread != nullptr); | 
|  | // Acquire the recorder lock in case we need to call |GetNewBlockLocked|. We | 
|  | // acquire the lock here and not directly before calls to |GetNewBlockLocked| | 
|  | // due to locking order restrictions. | 
|  | Mutex& recorder_lock = lock_; | 
|  | recorder_lock.Lock(); | 
|  | Mutex* thread_block_lock = thread->timeline_block_lock(); | 
|  | ASSERT(thread_block_lock != nullptr); | 
|  | // 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 != nullptr) { | 
|  | T->IncrementNoSafepointScopeDepth(); | 
|  | } | 
|  | #endif  // defined(DEBUG) | 
|  |  | 
|  | TimelineEventBlock* thread_block = thread->TimelineBlockLocked(); | 
|  |  | 
|  | if ((thread_block != nullptr) && thread_block->IsFull()) { | 
|  | // Thread has a block and it is full: | 
|  | // 1) Mark it as finished. | 
|  | thread->SetTimelineBlockLocked(nullptr); | 
|  | FinishBlock(thread_block); | 
|  | // 2) Allocate a new block. | 
|  | // We release |thread_block_lock| before calling |GetNewBlockLocked| to | 
|  | // avoid TSAN warnings about lock order inversion. | 
|  | thread_block_lock->Unlock(); | 
|  | thread_block = GetNewBlockLocked(); | 
|  | thread_block_lock->Lock(); | 
|  | thread->SetTimelineBlockLocked(thread_block); | 
|  | } else if (thread_block == nullptr) { | 
|  | // Thread has no block. Attempt to allocate one. | 
|  | // We release |thread_block_lock| before calling |GetNewBlockLocked| to | 
|  | // avoid TSAN warnings about lock order inversion. | 
|  | thread_block_lock->Unlock(); | 
|  | thread_block = GetNewBlockLocked(); | 
|  | thread_block_lock->Lock(); | 
|  | thread->SetTimelineBlockLocked(thread_block); | 
|  | } | 
|  | recorder_lock.Unlock(); | 
|  | if (thread_block != nullptr) { | 
|  | // NOTE: We are exiting this function with the thread's block lock held. | 
|  | ASSERT(!thread_block->IsFull()); | 
|  | TimelineEvent* event = thread_block->StartEventLocked(); | 
|  | return event; | 
|  | } | 
|  | // Drop lock here as no event is being handed out. | 
|  | #if defined(DEBUG) | 
|  | if (T != nullptr) { | 
|  | T->DecrementNoSafepointScopeDepth(); | 
|  | } | 
|  | #endif  // defined(DEBUG) | 
|  | thread_block_lock->Unlock(); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | 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 == nullptr) { | 
|  | return; | 
|  | } | 
|  | #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  | // Async track metadata is only written in Perfetto traces, and Perfetto | 
|  | // traces cannot be written when SUPPORT_PERFETTO is not defined, or when | 
|  | // PRODUCT is defined. | 
|  | if (event->event_type() == TimelineEvent::kAsyncBegin || | 
|  | event->event_type() == TimelineEvent::kAsyncInstant) { | 
|  | AddAsyncTrackMetadataBasedOnEvent(*event); | 
|  | } | 
|  | #endif  // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  | // Grab the current thread. | 
|  | OSThread* thread = OSThread::Current(); | 
|  | ASSERT(thread != nullptr); | 
|  | // Unlock the thread's block lock. | 
|  | Mutex* thread_block_lock = thread->timeline_block_lock(); | 
|  | ASSERT(thread_block_lock != nullptr); | 
|  | #if defined(DEBUG) | 
|  | Thread* T = Thread::Current(); | 
|  | if (T != nullptr) { | 
|  | T->DecrementNoSafepointScopeDepth(); | 
|  | } | 
|  | #endif  // defined(DEBUG) | 
|  | thread_block_lock->Unlock(); | 
|  | } | 
|  |  | 
|  | #ifndef PRODUCT | 
|  | void TimelineEventRecorder::WriteTo(const char* directory) { | 
|  | 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 == nullptr) || (file_write == nullptr) || | 
|  | (file_close == nullptr)) { | 
|  | OS::PrintErr("warning: Could not access file callbacks."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Acquire the recorder's lock to prevent the reclaimed blocks from being | 
|  | // handed out again until the trace has been serialized. | 
|  | MutexLocker ml(&lock_); | 
|  | Timeline::ReclaimCachedBlocksFromThreads(); | 
|  |  | 
|  | intptr_t pid = OS::ProcessId(); | 
|  | char* filename = | 
|  | OS::SCreate(nullptr, "%s/dart-timeline-%" Pd ".json", directory, pid); | 
|  | void* file = (*file_open)(filename, true); | 
|  | if (file == nullptr) { | 
|  | OS::PrintErr("warning: 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 = nullptr; | 
|  | 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; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | void TimelineEventRecorder::FinishBlock(TimelineEventBlock* block) { | 
|  | if (block != nullptr) { | 
|  | block->Finish(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEventRecorder::AddTrackMetadataBasedOnThread( | 
|  | const intptr_t process_id, | 
|  | const intptr_t trace_id, | 
|  | const char* thread_name) { | 
|  | ASSERT(FLAG_timeline_recorder != nullptr); | 
|  | if (strcmp("none", FLAG_timeline_recorder) == 0 || | 
|  | strcmp("callback", FLAG_timeline_recorder) == 0 || | 
|  | strcmp("systrace", FLAG_timeline_recorder) == 0 || | 
|  | FLAG_systrace_timeline) { | 
|  | // There is no way to retrieve track metadata when a no-op, callback, or | 
|  | // systrace recorder is in use, so we don't need to update the map in these | 
|  | // cases. | 
|  | return; | 
|  | } | 
|  | MutexLocker ml(&track_uuid_to_track_metadata_lock_); | 
|  |  | 
|  | void* key = reinterpret_cast<void*>(trace_id); | 
|  | const intptr_t hash = Utils::WordHash(trace_id); | 
|  | SimpleHashMap::Entry* entry = | 
|  | track_uuid_to_track_metadata_.Lookup(key, hash, true); | 
|  | if (entry->value == nullptr) { | 
|  | entry->value = new TimelineTrackMetadata( | 
|  | process_id, trace_id, | 
|  | CStringUniquePtr( | 
|  | Utils::StrDup(thread_name == nullptr ? "" : thread_name))); | 
|  | } else { | 
|  | TimelineTrackMetadata* value = | 
|  | static_cast<TimelineTrackMetadata*>(entry->value); | 
|  | ASSERT(process_id == value->pid()); | 
|  | value->set_track_name(CStringUniquePtr( | 
|  | Utils::StrDup(thread_name == nullptr ? "" : thread_name))); | 
|  | } | 
|  | } | 
|  |  | 
|  | #if !defined(PRODUCT) | 
|  | void TimelineEventRecorder::AddAsyncTrackMetadataBasedOnEvent( | 
|  | const TimelineEvent& event) { | 
|  | ASSERT(FLAG_timeline_recorder != nullptr); | 
|  | if (strcmp("none", FLAG_timeline_recorder) == 0 || | 
|  | strcmp("callback", FLAG_timeline_recorder) == 0 || | 
|  | strcmp("systrace", FLAG_timeline_recorder) == 0 || | 
|  | FLAG_systrace_timeline) { | 
|  | // There is no way to retrieve track metadata when a no-op, callback, or | 
|  | // systrace recorder is in use, so we don't need to update the map in | 
|  | // these cases. | 
|  | return; | 
|  | } | 
|  | MutexLocker ml(&async_track_uuid_to_track_metadata_lock_); | 
|  |  | 
|  | void* key = reinterpret_cast<void*>(event.Id()); | 
|  | const intptr_t hash = Utils::WordHash(event.Id()); | 
|  | SimpleHashMap::Entry* entry = | 
|  | async_track_uuid_to_track_metadata_.Lookup(key, hash, true); | 
|  | if (entry->value == nullptr) { | 
|  | entry->value = new AsyncTimelineTrackMetadata(OS::ProcessId(), event.Id()); | 
|  | } | 
|  | } | 
|  | #endif  // !defined(PRODUCT) | 
|  |  | 
|  | TimelineEventFixedBufferRecorder::TimelineEventFixedBufferRecorder( | 
|  | intptr_t capacity) | 
|  | : memory_(nullptr), | 
|  | blocks_(nullptr), | 
|  | 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 executable = false; | 
|  | const bool compressed = false; | 
|  | memory_ = | 
|  | VirtualMemory::Allocate(size, executable, compressed, "dart-timeline"); | 
|  | if (memory_ == nullptr) { | 
|  | OUT_OF_MEMORY(); | 
|  | } | 
|  | blocks_ = reinterpret_cast<TimelineEventBlock*>(memory_->address()); | 
|  | } | 
|  |  | 
|  | TimelineEventFixedBufferRecorder::~TimelineEventFixedBufferRecorder() { | 
|  | // We do not need to acquire any locks, because at this point we must have | 
|  | // reclaimed all the blocks, and |RecorderSynchronizationLock| must have been | 
|  | // put in a state that prevents blocks from being given out. | 
|  | delete memory_; | 
|  | } | 
|  |  | 
|  | intptr_t TimelineEventFixedBufferRecorder::Size() { | 
|  | return memory_->size(); | 
|  | } | 
|  |  | 
|  | #ifndef PRODUCT | 
|  | void TimelineEventFixedBufferRecorder::ForEachNonEmptyBlock( | 
|  | std::function<void(const TimelineEventBlock&)>&& handle_block) { | 
|  | // Acquire the recorder's lock to prevent the reclaimed blocks from being | 
|  | // handed out again until the trace has been serialized. | 
|  | MutexLocker ml(&lock_); | 
|  | Timeline::ReclaimCachedBlocksFromThreads(); | 
|  | ResetTimeTracking(); | 
|  | intptr_t block_offset = FindOldestBlockIndexLocked(); | 
|  | if (block_offset == -1) { | 
|  | // All blocks are in use or empty. | 
|  | return; | 
|  | } | 
|  | for (intptr_t block_idx = 0; block_idx < num_blocks_; block_idx++) { | 
|  | TimelineEventBlock* block = | 
|  | &blocks_[(block_idx + block_offset) % num_blocks_]; | 
|  | if (!block->ContainsEventsThatCanBeSerializedLocked()) { | 
|  | continue; | 
|  | } | 
|  | handle_block(*block); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEventBufferedRecorder::PrintEventsCommon( | 
|  | const TimelineEventFilter& filter, | 
|  | std::function<void(const TimelineEvent&)>&& print_impl) { | 
|  | ForEachNonEmptyBlock( | 
|  | [this, &filter, print_impl = std::move(print_impl)](auto& block) { | 
|  | for (intptr_t event_idx = 0, length = block.length(); | 
|  | event_idx < length; event_idx++) { | 
|  | auto 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()); | 
|  | print_impl(*event); | 
|  | } | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | void TimelineEventBufferedRecorder::PrintJSONEvents( | 
|  | const JSONArray& events, | 
|  | const TimelineEventFilter& filter) { | 
|  | PrintEventsCommon(filter, [&events](const TimelineEvent& event) { | 
|  | events.AddValue(&event); | 
|  | }); | 
|  | } | 
|  |  | 
|  | #if defined(SUPPORT_PERFETTO) | 
|  | void TimelineEventBufferedRecorder::PrintPerfettoEvents( | 
|  | JSONBase64String* jsonBase64String, | 
|  | const TimelineEventFilter& filter) { | 
|  | TracePacketWriter writer( | 
|  | packet(), | 
|  | [&jsonBase64String](auto& packet) { | 
|  | perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, | 
|  | &packet); | 
|  | }, | 
|  | FLAG_intern_strings_when_writing_perfetto_timeline); | 
|  |  | 
|  | PrintEventsCommon(filter, [&writer](const TimelineEvent& event) { | 
|  | writer.WriteEvent(event); | 
|  | }); | 
|  | } | 
|  | #endif  // defined(SUPPORT_PERFETTO) | 
|  |  | 
|  | void TimelineEventBufferedRecorder::PrintJSON(JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | JSONObject topLevel(js); | 
|  | topLevel.AddProperty("type", "Timeline"); | 
|  | { | 
|  | JSONArray events(&topLevel, "traceEvents"); | 
|  | PrintJSONMeta(events); | 
|  | PrintJSONEvents(events, *filter); | 
|  | } | 
|  | topLevel.AddPropertyTimeMicros("timeOriginMicros", TimeOriginMicros()); | 
|  | topLevel.AddPropertyTimeMicros("timeExtentMicros", TimeExtentMicros()); | 
|  | } | 
|  |  | 
|  | #if defined(SUPPORT_PERFETTO) | 
|  | void TimelineEventBufferedRecorder::PrintPerfettoTimeline( | 
|  | JSONStream* js, | 
|  | const TimelineEventFilter& filter) { | 
|  | JSONObject jsobj_topLevel(js); | 
|  | jsobj_topLevel.AddProperty("type", "PerfettoTimeline"); | 
|  |  | 
|  | js->AppendSerializedObject("\"trace\":"); | 
|  | { | 
|  | JSONBase64String jsonBase64String(js); | 
|  | PrintPerfettoMeta(&jsonBase64String); | 
|  | PrintPerfettoEvents(&jsonBase64String, filter); | 
|  | } | 
|  |  | 
|  | jsobj_topLevel.AddPropertyTimeMicros("timeOriginMicros", TimeOriginMicros()); | 
|  | jsobj_topLevel.AddPropertyTimeMicros("timeExtentMicros", TimeExtentMicros()); | 
|  | } | 
|  | #endif  // defined(SUPPORT_PERFETTO) | 
|  |  | 
|  | void TimelineEventBufferedRecorder::PrintTraceEvent( | 
|  | JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | JSONArray events(js); | 
|  | PrintJSONMeta(events); | 
|  | PrintJSONEvents(events, *filter); | 
|  | } | 
|  | #endif  // !defined(PRODUCT) | 
|  |  | 
|  | void TimelineEventFixedBufferRecorder::ClearLocked() { | 
|  | ASSERT(lock_.IsOwnedByCurrentThread()); | 
|  | for (intptr_t i = 0; i < num_blocks_; i++) { | 
|  | TimelineEventBlock* block = &blocks_[i]; | 
|  | block->Reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | intptr_t TimelineEventFixedBufferRecorder::FindOldestBlockIndexLocked() const { | 
|  | ASSERT(lock_.IsOwnedByCurrentThread()); | 
|  | 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->ContainsEventsThatCanBeSerializedLocked()) { | 
|  | // Skip in use and 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 == nullptr) { | 
|  | return; | 
|  | } | 
|  | ThreadBlockCompleteEvent(event); | 
|  | } | 
|  |  | 
|  | TimelineEventBlock* TimelineEventRingRecorder::GetNewBlockLocked() { | 
|  | ASSERT(lock_.IsOwnedByCurrentThread()); | 
|  | if (block_cursor_ == num_blocks_) { | 
|  | block_cursor_ = 0; | 
|  | } | 
|  | TimelineEventBlock* block = &blocks_[block_cursor_++]; | 
|  | if (block->current_owner_ != nullptr) { | 
|  | MutexLocker ml(block->current_owner_->timeline_block_lock()); | 
|  | block->current_owner_->SetTimelineBlockLocked(nullptr); | 
|  | block->Reset(); | 
|  | block->Open(); | 
|  | } else { | 
|  | block->Reset(); | 
|  | block->Open(); | 
|  | } | 
|  | return block; | 
|  | } | 
|  |  | 
|  | TimelineEventBlock* TimelineEventStartupRecorder::GetNewBlockLocked() { | 
|  | ASSERT(lock_.IsOwnedByCurrentThread()); | 
|  | if (block_cursor_ == num_blocks_) { | 
|  | return nullptr; | 
|  | } | 
|  | TimelineEventBlock* block = &blocks_[block_cursor_++]; | 
|  | block->Reset(); | 
|  | block->Open(); | 
|  | return block; | 
|  | } | 
|  |  | 
|  | TimelineEventCallbackRecorder::TimelineEventCallbackRecorder() {} | 
|  |  | 
|  | TimelineEventCallbackRecorder::~TimelineEventCallbackRecorder() {} | 
|  |  | 
|  | #ifndef PRODUCT | 
|  | void TimelineEventCallbackRecorder::PrintJSON(JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | UNREACHABLE(); | 
|  | } | 
|  |  | 
|  | #if defined(SUPPORT_PERFETTO) | 
|  | void TimelineEventCallbackRecorder::PrintPerfettoTimeline( | 
|  | JSONStream* js, | 
|  | const TimelineEventFilter& filter) { | 
|  | UNREACHABLE(); | 
|  | } | 
|  | #endif  // defined(SUPPORT_PERFETTO) | 
|  |  | 
|  | void TimelineEventCallbackRecorder::PrintTraceEvent( | 
|  | JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | JSONArray events(js); | 
|  | } | 
|  | #endif  // !defined(PRODUCT) | 
|  |  | 
|  | TimelineEvent* TimelineEventCallbackRecorder::StartEvent() { | 
|  | TimelineEvent* event = new TimelineEvent(); | 
|  | return event; | 
|  | } | 
|  |  | 
|  | void TimelineEventCallbackRecorder::CompleteEvent(TimelineEvent* event) { | 
|  | OnEvent(event); | 
|  | delete event; | 
|  | } | 
|  |  | 
|  | void TimelineEventEmbedderCallbackRecorder::OnEvent(TimelineEvent* event) { | 
|  | Dart_TimelineRecorderCallback callback = Timeline::callback(); | 
|  | if (callback == nullptr) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | Dart_TimelineRecorderEvent recorder_event; | 
|  | recorder_event.version = DART_TIMELINE_RECORDER_CURRENT_VERSION; | 
|  | switch (event->event_type()) { | 
|  | case TimelineEvent::kBegin: | 
|  | recorder_event.type = Dart_Timeline_Event_Begin; | 
|  | break; | 
|  | case TimelineEvent::kEnd: | 
|  | recorder_event.type = Dart_Timeline_Event_End; | 
|  | break; | 
|  | case TimelineEvent::kInstant: | 
|  | recorder_event.type = Dart_Timeline_Event_Instant; | 
|  | break; | 
|  | case TimelineEvent::kDuration: | 
|  | recorder_event.type = Dart_Timeline_Event_Duration; | 
|  | break; | 
|  | case TimelineEvent::kAsyncBegin: | 
|  | recorder_event.type = Dart_Timeline_Event_Async_Begin; | 
|  | break; | 
|  | case TimelineEvent::kAsyncEnd: | 
|  | recorder_event.type = Dart_Timeline_Event_Async_End; | 
|  | break; | 
|  | case TimelineEvent::kAsyncInstant: | 
|  | recorder_event.type = Dart_Timeline_Event_Async_Instant; | 
|  | break; | 
|  | case TimelineEvent::kCounter: | 
|  | recorder_event.type = Dart_Timeline_Event_Counter; | 
|  | break; | 
|  | case TimelineEvent::kFlowBegin: | 
|  | recorder_event.type = Dart_Timeline_Event_Flow_Begin; | 
|  | break; | 
|  | case TimelineEvent::kFlowStep: | 
|  | recorder_event.type = Dart_Timeline_Event_Flow_Step; | 
|  | break; | 
|  | case TimelineEvent::kFlowEnd: | 
|  | recorder_event.type = Dart_Timeline_Event_Flow_End; | 
|  | break; | 
|  | default: | 
|  | // Type not expressible as Dart_Timeline_Event_Type: drop event. | 
|  | return; | 
|  | } | 
|  | recorder_event.timestamp0 = event->timestamp0(); | 
|  | recorder_event.timestamp1_or_id = event->timestamp1_or_id(); | 
|  | recorder_event.isolate = event->isolate_id(); | 
|  | recorder_event.isolate_group = event->isolate_group_id(); | 
|  | recorder_event.isolate_data = event->isolate_data(); | 
|  | recorder_event.isolate_group_data = event->isolate_group_data(); | 
|  | recorder_event.label = event->label(); | 
|  | recorder_event.stream = event->stream()->name(); | 
|  | recorder_event.argument_count = event->GetNumArguments(); | 
|  | recorder_event.arguments = | 
|  | reinterpret_cast<Dart_TimelineRecorderEvent_Argument*>( | 
|  | event->arguments()); | 
|  |  | 
|  | NoActiveIsolateScope no_active_isolate_scope; | 
|  | callback(&recorder_event); | 
|  | } | 
|  |  | 
|  | void TimelineEventNopRecorder::OnEvent(TimelineEvent* event) { | 
|  | // Do nothing. | 
|  | } | 
|  |  | 
|  | TimelineEventPlatformRecorder::TimelineEventPlatformRecorder() {} | 
|  |  | 
|  | TimelineEventPlatformRecorder::~TimelineEventPlatformRecorder() {} | 
|  |  | 
|  | #ifndef PRODUCT | 
|  | void TimelineEventPlatformRecorder::PrintJSON(JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | UNREACHABLE(); | 
|  | } | 
|  |  | 
|  | #if defined(SUPPORT_PERFETTO) | 
|  | void TimelineEventPlatformRecorder::PrintPerfettoTimeline( | 
|  | JSONStream* js, | 
|  | const TimelineEventFilter& filter) { | 
|  | UNREACHABLE(); | 
|  | } | 
|  | #endif  // defined(SUPPORT_PERFETTO) | 
|  |  | 
|  | void TimelineEventPlatformRecorder::PrintTraceEvent( | 
|  | JSONStream* js, | 
|  | TimelineEventFilter* filter) { | 
|  | JSONArray events(js); | 
|  | } | 
|  | #endif  // !defined(PRODUCT) | 
|  |  | 
|  | TimelineEvent* TimelineEventPlatformRecorder::StartEvent() { | 
|  | TimelineEvent* event = new TimelineEvent(); | 
|  | return event; | 
|  | } | 
|  |  | 
|  | void TimelineEventPlatformRecorder::CompleteEvent(TimelineEvent* event) { | 
|  | OnEvent(event); | 
|  | delete event; | 
|  | } | 
|  |  | 
|  | static void TimelineEventFileRecorderBaseStart(uword parameter) { | 
|  | reinterpret_cast<TimelineEventFileRecorderBase*>(parameter)->Drain(); | 
|  | } | 
|  |  | 
|  | TimelineEventFileRecorderBase::TimelineEventFileRecorderBase(const char* path) | 
|  | : TimelineEventRecorder() { | 
|  | 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 == nullptr) || (file_write == nullptr) || | 
|  | (file_close == nullptr)) { | 
|  | OS::PrintErr("warning: Could not access file callbacks."); | 
|  | return; | 
|  | } | 
|  | void* file = (*file_open)(path, true); | 
|  | if (file == nullptr) { | 
|  | OS::PrintErr("warning: Failed to open timeline file: %s\n", path); | 
|  | return; | 
|  | } | 
|  |  | 
|  | file_ = file; | 
|  | } | 
|  |  | 
|  | TimelineEventFileRecorderBase::~TimelineEventFileRecorderBase() { | 
|  | // WARNING: |ShutDown()| must be called in the derived class destructor. This | 
|  | // work cannot be performed in this destructor, because then |DrainImpl()| | 
|  | // might run between when the derived class destructor completes, and when | 
|  | // |shutting_down_| is set to true, causing possible use-after-free errors. | 
|  | ASSERT(shutting_down_); | 
|  |  | 
|  | if (file_ == nullptr) return; | 
|  |  | 
|  | ASSERT(thread_id_ != OSThread::kInvalidThreadJoinId); | 
|  | OSThread::Join(thread_id_); | 
|  | thread_id_ = OSThread::kInvalidThreadJoinId; | 
|  |  | 
|  | // At this point all blocks created by this recorder should be drained | 
|  | // and placed into |empty_blocks_|. Delete all of them. | 
|  | ASSERT(drained_); | 
|  | ASSERT(completed_blocks_head_ == nullptr); | 
|  | ASSERT(completed_blocks_tail_ == nullptr); | 
|  | while (empty_blocks_ != nullptr) { | 
|  | auto block = empty_blocks_; | 
|  | empty_blocks_ = empty_blocks_->next(); | 
|  | delete block; | 
|  | block_count_--; | 
|  | } | 
|  | ASSERT(block_count_ == 0); | 
|  |  | 
|  | FlushBuffer(); | 
|  |  | 
|  | Dart_FileCloseCallback file_close = Dart::file_close_callback(); | 
|  | (*file_close)(file_); | 
|  | file_ = nullptr; | 
|  | } | 
|  |  | 
|  | void TimelineEventFileRecorderBase::Drain() { | 
|  | MonitorLocker ml(&monitor_); | 
|  | thread_id_ = OSThread::GetCurrentThreadJoinId(OSThread::Current()); | 
|  | ml.Notify(); | 
|  | for (;;) { | 
|  | if (completed_blocks_head_ == nullptr) { | 
|  | ASSERT(completed_blocks_tail_ == nullptr); | 
|  | if (shutting_down_) { | 
|  | break; | 
|  | } | 
|  | ml.Wait(); | 
|  | continue;  // Recheck empty. | 
|  | } | 
|  | // Take the whole list of pending blocks and drain all of them. | 
|  | auto blocks = completed_blocks_head_; | 
|  | completed_blocks_tail_ = completed_blocks_head_ = nullptr; | 
|  | { | 
|  | MonitorLeaveScope leave_ml(&ml); | 
|  | DrainBlockChain(blocks); | 
|  | } | 
|  | } | 
|  | drained_ = true; | 
|  | ml.Notify(); | 
|  | } | 
|  |  | 
|  | void TimelineEventFileRecorderBase::DrainBlockChain(TimelineEventBlock* block) { | 
|  | while (block != nullptr) { | 
|  | auto next_block = block->next(); | 
|  | block->set_next(nullptr); | 
|  |  | 
|  | for (intptr_t i = 0, length = block->length(); i < length; i++) { | 
|  | DrainImpl(*block->At(i)); | 
|  | } | 
|  | block->Reset(); | 
|  |  | 
|  | // Place block for reuse. | 
|  | { | 
|  | MonitorLocker ml(&monitor_); | 
|  | block->set_next(empty_blocks_); | 
|  | empty_blocks_ = block; | 
|  | } | 
|  | block = next_block; | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEventFileRecorderBase::FlushBuffer() { | 
|  | if (buffer_pos_ != 0) { | 
|  | WriteToFile(buffer_.get(), buffer_pos_); | 
|  | buffer_pos_ = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEventFileRecorderBase::Write(const char* bytes, intptr_t length) { | 
|  | if (length >= kBufferSize / 2) { | 
|  | FlushBuffer(); | 
|  | WriteToFile(bytes, length); | 
|  | return; | 
|  | } | 
|  |  | 
|  | do { | 
|  | intptr_t space_left = kBufferSize - buffer_pos_; | 
|  | intptr_t bytes_to_write = Utils::Minimum(length, space_left); | 
|  | memcpy(buffer_.get() + buffer_pos_, bytes, bytes_to_write);  // NOLINT | 
|  | buffer_pos_ += bytes_to_write; | 
|  | length -= bytes_to_write; | 
|  | bytes += bytes_to_write; | 
|  | if (buffer_pos_ == kBufferSize) { | 
|  | FlushBuffer(); | 
|  | } | 
|  | } while (length > 0); | 
|  | } | 
|  |  | 
|  | void TimelineEventFileRecorderBase::WriteToFile(const char* buffer, | 
|  | intptr_t len) const { | 
|  | Dart_FileWriteCallback file_write = Dart::file_write_callback(); | 
|  | (*file_write)(buffer, len, file_); | 
|  | } | 
|  |  | 
|  | TimelineEvent* TimelineEventFileRecorderBase::StartEvent() { | 
|  | return ThreadBlockStartEvent(); | 
|  | } | 
|  |  | 
|  | void TimelineEventFileRecorderBase::CompleteEvent(TimelineEvent* event) { | 
|  | if (event == nullptr) { | 
|  | return; | 
|  | } | 
|  | if (file_ == nullptr) { | 
|  | delete event; | 
|  | return; | 
|  | } | 
|  | ThreadBlockCompleteEvent(event); | 
|  | } | 
|  |  | 
|  | void TimelineEventFileRecorderBase::FinishBlock(TimelineEventBlock* block) { | 
|  | TimelineEventRecorder::FinishBlock(block); | 
|  |  | 
|  | if (block != nullptr) { | 
|  | // Append completed block to the end of the list of completed blocks. | 
|  | // We want to keep events from the same thread ordered in sequentially | 
|  | // in the output. | 
|  | MonitorLocker ml(&monitor_); | 
|  | block->set_next(nullptr); | 
|  | if (completed_blocks_tail_ != nullptr) { | 
|  | completed_blocks_tail_->set_next(block); | 
|  | completed_blocks_tail_ = block; | 
|  | } else { | 
|  | completed_blocks_head_ = completed_blocks_tail_ = block; | 
|  | } | 
|  | ml.Notify(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEventFileRecorderBase::StartUp(const char* name) { | 
|  | OSThread::Start(name, TimelineEventFileRecorderBaseStart, | 
|  | reinterpret_cast<uword>(this)); | 
|  |  | 
|  | MonitorLocker ml(&monitor_); | 
|  | while (thread_id_ == OSThread::kInvalidThreadJoinId) { | 
|  | ml.Wait(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Must be called in derived class destructors. | 
|  | // See |~TimelineEventFileRecorderBase()| for an explanation. | 
|  | void TimelineEventFileRecorderBase::ShutDown() { | 
|  | MonitorLocker ml(&monitor_); | 
|  | shutting_down_ = true; | 
|  | ml.NotifyAll(); | 
|  | while (!drained_) { | 
|  | ml.Wait(); | 
|  | } | 
|  | } | 
|  |  | 
|  | TimelineEventBlock* TimelineEventFileRecorderBase::GetNewBlockLocked() { | 
|  | ASSERT(lock_.IsOwnedByCurrentThread()); | 
|  | // Start by reusing a block. | 
|  | TimelineEventBlock* block = nullptr; | 
|  | if (empty_blocks_ != nullptr) { | 
|  | // TODO(vegorov) maybe we don't want to take a lock just to grab an empty | 
|  | // block? | 
|  | MonitorLocker ml(&monitor_); | 
|  | if (empty_blocks_ != nullptr) { | 
|  | block = empty_blocks_; | 
|  | empty_blocks_ = empty_blocks_->next(); | 
|  | if (FLAG_trace_timeline) { | 
|  | OS::PrintErr("Reused empty block %p\n", block); | 
|  | } | 
|  | } | 
|  | } | 
|  | if (block == nullptr) { | 
|  | block = new TimelineEventBlock(block_count_++); | 
|  | if (FLAG_trace_timeline) { | 
|  | OS::PrintErr("Created new block %p\n", block); | 
|  | } | 
|  | } | 
|  | block->Open(); | 
|  |  | 
|  | return block; | 
|  | } | 
|  |  | 
|  | void TimelineEventFileRecorderBase::ClearLocked() { | 
|  | ASSERT(lock_.IsOwnedByCurrentThread()); | 
|  | } | 
|  |  | 
|  | TimelineEventFileRecorder::TimelineEventFileRecorder(const char* path) | 
|  | : TimelineEventFileRecorderBase(path), first_(true) { | 
|  | // Chrome trace format has two forms: | 
|  | //   Object form:  { "traceEvents": [ event, event, event ] } | 
|  | //   Array form:   [ event, event, event ] | 
|  | // For this recorder, we use the array form because Catapult will handle a | 
|  | // missing ending bracket in this form in case we don't cleanly end the | 
|  | // trace. | 
|  | Write("[\n"); | 
|  | StartUp("TimelineEventFileRecorder"); | 
|  | } | 
|  |  | 
|  | TimelineEventFileRecorder::~TimelineEventFileRecorder() { | 
|  | ShutDown(); | 
|  | Write("]\n"); | 
|  | } | 
|  |  | 
|  | void TimelineEventFileRecorder::AddTrackMetadataBasedOnThread( | 
|  | const intptr_t process_id, | 
|  | const intptr_t trace_id, | 
|  | const char* thread_name) { | 
|  | TimelineEvent* event = StartEvent(); | 
|  | event->Metadata("thread_name"); | 
|  | event->SetNumArguments(1); | 
|  | event->CopyArgument(0, "name", thread_name); | 
|  | CompleteEvent(event); | 
|  | } | 
|  |  | 
|  | void TimelineEventFileRecorder::DrainImpl(const TimelineEvent& event) { | 
|  | JSONWriter writer; | 
|  | if (first_) { | 
|  | first_ = false; | 
|  | } else { | 
|  | writer.buffer()->AddChar(','); | 
|  | } | 
|  | event.PrintJSON(&writer); | 
|  | char* output = nullptr; | 
|  | intptr_t output_length = 0; | 
|  | writer.Steal(&output, &output_length); | 
|  | Write(output, output_length); | 
|  | free(output); | 
|  | } | 
|  |  | 
|  | #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  | class TimelineEventPerfettoFileRecorder : public TimelineEventFileRecorderBase { | 
|  | public: | 
|  | explicit TimelineEventPerfettoFileRecorder(const char* path); | 
|  | virtual ~TimelineEventPerfettoFileRecorder(); | 
|  |  | 
|  | const char* name() const final { return PERFETTO_FILE_RECORDER_NAME; } | 
|  |  | 
|  | private: | 
|  | void WritePacket( | 
|  | protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* packet); | 
|  | void DrainImpl(const TimelineEvent& event) final; | 
|  |  | 
|  | TracePacketWriter writer_; | 
|  | }; | 
|  |  | 
|  | static TimelineEventRecorder* CreateTimelineEventPerfettoFileRecorder( | 
|  | const char* filename) { | 
|  | return new TimelineEventPerfettoFileRecorder(filename); | 
|  | } | 
|  |  | 
|  | TimelineEventPerfettoFileRecorder::TimelineEventPerfettoFileRecorder( | 
|  | const char* path) | 
|  | : TimelineEventFileRecorderBase(path), | 
|  | writer_( | 
|  | packet(), | 
|  | [this](auto& packet) { this->WritePacket(&packet); }, | 
|  | FLAG_intern_strings_when_writing_perfetto_timeline) { | 
|  | protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>& packet = | 
|  | this->packet(); | 
|  |  | 
|  | perfetto_utils::PopulateClockSnapshotPacket(packet.get()); | 
|  | WritePacket(&packet); | 
|  | packet.Reset(); | 
|  |  | 
|  | perfetto_utils::PopulateProcessDescriptorPacket(packet.get()); | 
|  | WritePacket(&packet); | 
|  | packet.Reset(); | 
|  |  | 
|  | StartUp("TimelineEventPerfettoFileRecorder"); | 
|  | } | 
|  |  | 
|  | TimelineEventPerfettoFileRecorder::~TimelineEventPerfettoFileRecorder() { | 
|  | ShutDown(); | 
|  |  | 
|  | protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>& packet = | 
|  | this->packet(); | 
|  | // We do not need to lock the following section, because at this point | 
|  | // |RecorderSynchronizationLock| must have been put in a state that prevents | 
|  | // the metadata maps from being modified. | 
|  | for (SimpleHashMap::Entry* entry = track_uuid_to_track_metadata().Start(); | 
|  | entry != nullptr; entry = track_uuid_to_track_metadata().Next(entry)) { | 
|  | TimelineTrackMetadata* value = | 
|  | static_cast<TimelineTrackMetadata*>(entry->value); | 
|  | value->PopulateTracePacket(packet.get()); | 
|  | WritePacket(&packet); | 
|  | packet.Reset(); | 
|  | } | 
|  | for (SimpleHashMap::Entry* entry = | 
|  | async_track_uuid_to_track_metadata().Start(); | 
|  | entry != nullptr; | 
|  | entry = async_track_uuid_to_track_metadata().Next(entry)) { | 
|  | AsyncTimelineTrackMetadata* value = | 
|  | static_cast<AsyncTimelineTrackMetadata*>(entry->value); | 
|  | value->PopulateTracePacket(packet.get()); | 
|  | WritePacket(&packet); | 
|  | packet.Reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEventPerfettoFileRecorder::WritePacket( | 
|  | protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* packet) { | 
|  | const std::tuple<std::unique_ptr<const uint8_t[]>, intptr_t>& response = | 
|  | perfetto_utils::GetProtoPreamble(packet); | 
|  | Write(reinterpret_cast<const char*>(std::get<0>(response).get()), | 
|  | std::get<1>(response)); | 
|  | for (const protozero::ScatteredHeapBuffer::Slice& slice : | 
|  | packet->GetSlices()) { | 
|  | Write(reinterpret_cast<const char*>(slice.start()), | 
|  | slice.size() - slice.unused_bytes()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TimelineEventPerfettoFileRecorder::DrainImpl(const TimelineEvent& event) { | 
|  | writer_.WriteEvent(event); | 
|  | if (event.event_type() == TimelineEvent::kAsyncBegin || | 
|  | event.event_type() == TimelineEvent::kAsyncInstant) { | 
|  | AddAsyncTrackMetadataBasedOnEvent(event); | 
|  | } | 
|  | } | 
|  | #endif  // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) | 
|  |  | 
|  | TimelineEventEndlessRecorder::TimelineEventEndlessRecorder() | 
|  | : head_(nullptr), tail_(nullptr), block_index_(0) {} | 
|  |  | 
|  | TimelineEventEndlessRecorder::~TimelineEventEndlessRecorder() { | 
|  | ASSERT(head_ == nullptr); | 
|  | } | 
|  |  | 
|  | #ifndef PRODUCT | 
|  | void TimelineEventEndlessRecorder::ForEachNonEmptyBlock( | 
|  | std::function<void(const TimelineEventBlock&)>&& handle_block) { | 
|  | // Acquire the recorder's lock to prevent the reclaimed blocks from being | 
|  | // handed out again until the trace has been serialized. | 
|  | MutexLocker ml(&lock_); | 
|  | Timeline::ReclaimCachedBlocksFromThreads(); | 
|  | ResetTimeTracking(); | 
|  | for (TimelineEventBlock* current = head_; current != nullptr; | 
|  | current = current->next()) { | 
|  | if (!current->ContainsEventsThatCanBeSerializedLocked()) { | 
|  | continue; | 
|  | } | 
|  | handle_block(*current); | 
|  | } | 
|  | } | 
|  | #endif  // !defined(PRODUCT) | 
|  |  | 
|  | TimelineEvent* TimelineEventEndlessRecorder::StartEvent() { | 
|  | return ThreadBlockStartEvent(); | 
|  | } | 
|  |  | 
|  | void TimelineEventEndlessRecorder::CompleteEvent(TimelineEvent* event) { | 
|  | if (event == nullptr) { | 
|  | return; | 
|  | } | 
|  | ThreadBlockCompleteEvent(event); | 
|  | } | 
|  |  | 
|  | TimelineEventBlock* TimelineEventEndlessRecorder::GetNewBlockLocked() { | 
|  | ASSERT(lock_.IsOwnedByCurrentThread()); | 
|  | TimelineEventBlock* block = new TimelineEventBlock(block_index_++); | 
|  | block->Open(); | 
|  | if (head_ == nullptr) { | 
|  | head_ = tail_ = block; | 
|  | } else { | 
|  | tail_->set_next(block); | 
|  | tail_ = block; | 
|  | } | 
|  | if (FLAG_trace_timeline) { | 
|  | OS::PrintErr("Created new block %p\n", block); | 
|  | } | 
|  | return block; | 
|  | } | 
|  |  | 
|  | void TimelineEventEndlessRecorder::ClearLocked() { | 
|  | ASSERT(lock_.IsOwnedByCurrentThread()); | 
|  | TimelineEventBlock* current = head_; | 
|  | while (current != nullptr) { | 
|  | TimelineEventBlock* next = current->next(); | 
|  | delete current; | 
|  | current = next; | 
|  | } | 
|  | head_ = nullptr; | 
|  | tail_ = nullptr; | 
|  | block_index_ = 0; | 
|  | } | 
|  |  | 
|  | TimelineEventBlock::TimelineEventBlock(intptr_t block_index) | 
|  | : next_(nullptr), | 
|  | length_(0), | 
|  | block_index_(block_index), | 
|  | current_owner_(nullptr), | 
|  | in_use_(false) {} | 
|  |  | 
|  | TimelineEventBlock::~TimelineEventBlock() { | 
|  | Reset(); | 
|  | } | 
|  |  | 
|  | #ifndef PRODUCT | 
|  | void TimelineEventBlock::PrintJSON(JSONStream* js) const { | 
|  | ASSERT(!InUseLocked()); | 
|  | JSONArray events(js); | 
|  | for (intptr_t i = 0; i < length(); i++) { | 
|  | const TimelineEvent* event = At(i); | 
|  | if (event->IsValid()) { | 
|  | events.AddValue(event); | 
|  | } | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | TimelineEvent* TimelineEventBlock::StartEventLocked() { | 
|  | OSThread* os_thread = OSThread::Current(); | 
|  | ASSERT(os_thread != nullptr); | 
|  | ASSERT(os_thread == current_owner_); | 
|  | ASSERT(os_thread->timeline_block_lock()->IsOwnedByCurrentThread()); | 
|  | ASSERT(!IsFull()); | 
|  | if (FLAG_trace_timeline) { | 
|  | 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(); | 
|  | } | 
|  |  | 
|  | void TimelineEventBlock::Reset() { | 
|  | for (intptr_t i = 0; i < kBlockSize; i++) { | 
|  | // Clear any extra data. | 
|  | events_[i].Reset(); | 
|  | } | 
|  | length_ = 0; | 
|  | current_owner_ = nullptr; | 
|  | in_use_ = false; | 
|  | } | 
|  |  | 
|  | void TimelineEventBlock::Open() { | 
|  | OSThread* os_thread = OSThread::Current(); | 
|  | ASSERT(os_thread != nullptr); | 
|  | current_owner_ = os_thread; | 
|  | in_use_ = true; | 
|  | } | 
|  |  | 
|  | void TimelineEventBlock::Finish() { | 
|  | if (FLAG_trace_timeline) { | 
|  | OS::PrintErr("Finish block %p\n", this); | 
|  | } | 
|  | current_owner_ = nullptr; | 
|  | in_use_ = false; | 
|  | #ifndef PRODUCT | 
|  | if (Service::timeline_stream.enabled()) { | 
|  | ServiceEvent service_event(ServiceEvent::kTimelineEvents); | 
|  | service_event.set_timeline_event_block(this); | 
|  | Service::HandleEvent(&service_event, /* enter_safepoint */ false); | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void DartTimelineEventHelpers::ReportTaskEvent( | 
|  | TimelineEvent* event, | 
|  | int64_t id, | 
|  | intptr_t flow_id_count, | 
|  | std::unique_ptr<const int64_t[]>& flow_ids, | 
|  | intptr_t type, | 
|  | char* name, | 
|  | char* args) { | 
|  | const int64_t start = OS::GetCurrentMonotonicMicrosForTimeline(); | 
|  | switch (static_cast<TimelineEvent::EventType>(type)) { | 
|  | case TimelineEvent::kAsyncInstant: | 
|  | event->AsyncInstant(name, id, start); | 
|  | break; | 
|  | case TimelineEvent::kAsyncBegin: | 
|  | event->AsyncBegin(name, id, start); | 
|  | break; | 
|  | case TimelineEvent::kAsyncEnd: | 
|  | event->AsyncEnd(name, id, start); | 
|  | break; | 
|  | case TimelineEvent::kBegin: | 
|  | event->Begin(name, id, start); | 
|  | break; | 
|  | case TimelineEvent::kEnd: | 
|  | event->End(name, id, start); | 
|  | break; | 
|  | case TimelineEvent::kFlowBegin: | 
|  | event->FlowBegin(name, id, start); | 
|  | break; | 
|  | case TimelineEvent::kFlowStep: | 
|  | event->FlowStep(name, id, start); | 
|  | break; | 
|  | case TimelineEvent::kFlowEnd: | 
|  | event->FlowEnd(name, id, start); | 
|  | break; | 
|  | case TimelineEvent::kInstant: | 
|  | event->Instant(name, start); | 
|  | break; | 
|  | default: | 
|  | UNREACHABLE(); | 
|  | } | 
|  | if (flow_id_count > 0) { | 
|  | ASSERT(type == TimelineEvent::kBegin || type == TimelineEvent::kInstant || | 
|  | type == TimelineEvent::kAsyncBegin || | 
|  | type == TimelineEvent::kAsyncInstant); | 
|  |  | 
|  | event->SetFlowIds(flow_id_count, flow_ids); | 
|  | } | 
|  | event->set_owns_label(true); | 
|  | event->CompleteWithPreSerializedArgs(args); | 
|  | } | 
|  |  | 
|  | }  // namespace dart | 
|  |  | 
|  | #endif  // defined(SUPPORT_TIMELINE) |