| // 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/image_snapshot.h" | |
| #include "vm/isolate.h" | |
| #include "vm/json_stream.h" | |
| #include "vm/lockers.h" | |
| #include "vm/log.h" | |
| #include "vm/object.h" | |
| #include "vm/reverse_pc_lookup_cache.h" | |
| #include "vm/service.h" | |
| #include "vm/service_event.h" | |
| #include "vm/thread.h" | |
| #if defined(SUPPORT_PERFETTO) | |
| #include "perfetto/ext/tracing/core/trace_packet.h" | |
| #include "third_party/perfetto/protos/perfetto/common/builtin_clock.pbzero.h" | |
| #include "third_party/perfetto/protos/perfetto/trace/clock_snapshot.pbzero.h" | |
| #include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h" | |
| #include "third_party/perfetto/protos/perfetto/trace/track_event/debug_annotation.pbzero.h" | |
| #include "third_party/perfetto/protos/perfetto/trace/track_event/process_descriptor.pbzero.h" | |
| #include "third_party/perfetto/protos/perfetto/trace/track_event/thread_descriptor.pbzero.h" | |
| #include "third_party/perfetto/protos/perfetto/trace/track_event/track_descriptor.pbzero.h" | |
| #include "third_party/perfetto/protos/perfetto/trace/track_event/track_event.pbzero.h" | |
| #include "vm/perfetto_utils.h" | |
| #endif // defined(SUPPORT_PERFETTO) | |
| 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 | |
| } | |
| static TimelineEventRecorder* CreateSystraceTimelineRecorder() { | |
| #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 | |
| return nullptr; | |
| #endif | |
| } | |
| #if defined(SUPPORT_PERFETTO) | |
| static TimelineEventRecorder* CreateTimelineEventPerfettoFileRecorder( | |
| const char* filename, | |
| bool intern_strings, | |
| const char** error); | |
| #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) { | |
| const auto recorder = CreateSystraceTimelineRecorder(); | |
| if (recorder != nullptr) { | |
| return recorder; | |
| } | |
| // Fall-through to emit a warning about unsupported recorder. | |
| } | |
| 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; | |
| const char* error = nullptr; | |
| void* file = TimelineEventFileRecorderBase::OpenTimelineFileForWriting( | |
| filename, &error); | |
| if (file != nullptr) { | |
| return new TimelineEventFileRecorder(file); | |
| } | |
| OS::PrintErr( | |
| "Warning: failed to initialize file timeline recorder %s: %s\n", | |
| filename, error); | |
| return CreateDefaultTimelineRecorder(); | |
| } | |
| if (strcmp("callback", flag) == 0) { | |
| return new TimelineEventEmbedderCallbackRecorder(); | |
| } | |
| #if defined(SUPPORT_PERFETTO) | |
| { | |
| 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; | |
| const char* error = nullptr; | |
| auto recorder = CreateTimelineEventPerfettoFileRecorder( | |
| filename, FLAG_intern_strings_when_writing_perfetto_timeline, &error); | |
| if (recorder != nullptr) { | |
| return recorder; | |
| } | |
| OS::PrintErr( | |
| "Warning: failed to initialize perfetto file timeline recorder %s: " | |
| "%s\n", | |
| filename, error); | |
| return CreateDefaultTimelineRecorder(); | |
| } | |
| } | |
| #endif // defined(SUPPORT_PERFETTO) | |
| #if !defined(PRODUCT) | |
| // 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 streams. | |
| static MallocGrowableArray<char*>* GetEnabledByDefaultTimelineStreams( | |
| const char* timeline_streams) { | |
| MallocGrowableArray<char*>* result = new MallocGrowableArray<char*>(); | |
| if (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(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() { | |
| InitWithRecorder(CreateTimelineRecorder(), FLAG_timeline_streams); | |
| } | |
| void Timeline::InitWithRecorder(TimelineEventRecorder* recorder, | |
| const char* timeline_streams) { | |
| ASSERT(recorder_ == nullptr); | |
| recorder_ = recorder; | |
| 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(timeline_streams); | |
| // 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(OSThreadIterator* it) { | |
| RecorderSynchronizationLockScope ls; | |
| TimelineEventRecorder* recorder = Timeline::recorder(); | |
| if (recorder == nullptr || ls.IsUninitialized()) { | |
| return; | |
| } | |
| ASSERT(recorder != nullptr); | |
| // Iterate over threads. | |
| 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. | |
| OSThreadIterator it; | |
| MutexLocker ml(&recorder->lock_); | |
| ReclaimCachedBlocksFromThreads(&it); | |
| 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_[i].value = nullptr; | |
| buffer_[i].name = nullptr; | |
| } | |
| 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; | |
| static std::atomic<bool> is_streaming_timeline{false}; | |
| static RelaxedAtomic<uint64_t> streaming_start_micros = 0; | |
| static RelaxedAtomic<uint64_t> streaming_stop_micros = kMaxInt64; | |
| #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) | |
| namespace { | |
| 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); | |
| } | |
| } | |
| perfetto_utils::InternedDataBuilder& interned_data_builder() { | |
| return interned_data_builder_; | |
| } | |
| 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_; | |
| perfetto_utils::InternedDataBuilder interned_data_builder_; | |
| DISALLOW_COPY_AND_ASSIGN(TracePacketWriter); | |
| }; | |
| } // namespace | |
| #endif // defined(SUPPORT_PERFETTO) | |
| 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"); | |
| } | |
| } | |
| #endif // !defined(PRODUCT) | |
| #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()); | |
| } | |
| AsyncTimelineTrackMetadata::AsyncTimelineTrackMetadata(intptr_t pid, | |
| intptr_t async_id) | |
| : pid_(pid), async_id_(async_id) {} | |
| 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) | |
| 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) {} | |
| 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; | |
| } | |
| } | |
| #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); | |
| } | |
| } | |
| #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) | |
| // Async track metadata is only written in Perfetto traces, and Perfetto | |
| // traces cannot be written when SUPPORT_PERFETTO is not defined. | |
| if (event->event_type() == TimelineEvent::kAsyncBegin || | |
| event->event_type() == TimelineEvent::kAsyncInstant) { | |
| AddAsyncTrackMetadataBasedOnEvent(*event); | |
| } | |
| #endif // defined(SUPPORT_PERFETTO) | |
| // 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. | |
| OSThreadIterator it; | |
| MutexLocker ml(&lock_); | |
| Timeline::ReclaimCachedBlocksFromThreads(&it); | |
| 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) { | |
| 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))); | |
| } | |
| } | |
| void TimelineEventRecorder::AddAsyncTrackMetadataBasedOnEvent( | |
| const TimelineEvent& event) {} | |
| #if defined(SUPPORT_PERFETTO) | |
| template <typename Base> | |
| template <typename... Args> | |
| TimelineEventRecorderWithPerfettoSupport< | |
| Base>::TimelineEventRecorderWithPerfettoSupport(Args&&... args) | |
| : Base(std::forward<Args>(args)...), | |
| async_track_uuid_to_track_metadata_lock_(), | |
| async_track_uuid_to_track_metadata_( | |
| &SimpleHashMap::SamePointerValue, | |
| TimelineEventRecorder::kTrackUuidToTrackMetadataInitialCapacity) {} | |
| template <typename Base> | |
| TimelineEventRecorderWithPerfettoSupport< | |
| Base>::~TimelineEventRecorderWithPerfettoSupport() { | |
| 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; | |
| } | |
| } | |
| template <typename Base> | |
| void TimelineEventRecorderWithPerfettoSupport< | |
| Base>::AddAsyncTrackMetadataBasedOnEvent(const TimelineEvent& event) { | |
| 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()); | |
| } | |
| } | |
| template <typename Base> | |
| void TimelineEventRecorderWithPerfettoSupport<Base>::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(&TimelineEventRecorder::track_uuid_to_track_metadata_lock()); | |
| for (SimpleHashMap::Entry* entry = | |
| TimelineEventRecorder::track_uuid_to_track_metadata().Start(); | |
| entry != nullptr; | |
| entry = TimelineEventRecorder::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) | |
| 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; | |
| memory_ = VirtualMemory::Allocate(size, executable, "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. | |
| OSThreadIterator it; | |
| MutexLocker ml(&lock_); | |
| Timeline::ReclaimCachedBlocksFromThreads(&it); | |
| 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 TimelineEventEmbedderCallbackRecorder::AddTrackMetadataBasedOnThread( | |
| const intptr_t process_id, | |
| const intptr_t trace_id, | |
| const char* thread_name) { | |
| // This information can not be retrieved or written out in this format. | |
| // Do nothing. | |
| } | |
| void TimelineEventNopRecorder::OnEvent(TimelineEvent* event) { | |
| // Do nothing. | |
| } | |
| void TimelineEventNopRecorder::AddTrackMetadataBasedOnThread( | |
| const intptr_t process_id, | |
| const intptr_t trace_id, | |
| const char* thread_name) { | |
| // This information can not be retrieved or written out in this format. | |
| // Do nothing. | |
| } | |
| TimelineEventPlatformRecorder::TimelineEventPlatformRecorder() {} | |
| TimelineEventPlatformRecorder::~TimelineEventPlatformRecorder() {} | |
| void TimelineEventPlatformRecorder::AddTrackMetadataBasedOnThread( | |
| const intptr_t process_id, | |
| const intptr_t trace_id, | |
| const char* thread_name) { | |
| // This information can not be retrieved or written out in this format. | |
| // Do nothing. | |
| } | |
| #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(); | |
| } | |
| void* TimelineEventFileRecorderBase::OpenTimelineFileForWriting( | |
| const char* path, | |
| const char** error) { | |
| 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)) { | |
| *error = "Could not access file callbacks."; | |
| return nullptr; | |
| } | |
| void* file = (*file_open)(path, true); | |
| if (file == nullptr) { | |
| *error = "Failed to open timeline file."; | |
| return nullptr; | |
| } | |
| return file; | |
| } | |
| TimelineEventFileRecorderBase::TimelineEventFileRecorderBase(void* file) | |
| : TimelineEventRecorder() { | |
| ASSERT(file != nullptr); | |
| 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_); | |
| 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; | |
| } | |
| 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()); | |
| TimelineEventBlock* block = nullptr; | |
| { | |
| MonitorLocker ml(&monitor_); | |
| if (empty_blocks_ != nullptr) { | |
| block = empty_blocks_; | |
| empty_blocks_ = empty_blocks_->next(); | |
| } | |
| } | |
| if (block == nullptr) { | |
| block = new TimelineEventBlock(block_count_++); | |
| if (FLAG_trace_timeline) { | |
| OS::PrintErr("Created new block %p\n", block); | |
| } | |
| } else if (FLAG_trace_timeline) { | |
| OS::PrintErr("Reused empty block %p\n", block); | |
| } | |
| block->Open(); | |
| return block; | |
| } | |
| void TimelineEventFileRecorderBase::ClearLocked() { | |
| ASSERT(lock_.IsOwnedByCurrentThread()); | |
| } | |
| TimelineEventFileRecorder::TimelineEventFileRecorder(void* file) | |
| : TimelineEventFileRecorderBase(file), 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) | |
| class TimelineEventPerfettoFileRecorder | |
| : public TimelineEventRecorderWithPerfettoSupport< | |
| TimelineEventFileRecorderBase> { | |
| public: | |
| explicit TimelineEventPerfettoFileRecorder(void* file, bool intern_strings); | |
| virtual ~TimelineEventPerfettoFileRecorder(); | |
| const char* name() const final { return PERFETTO_FILE_RECORDER_NAME; } | |
| #if defined(DART_INCLUDE_PROFILER) | |
| void WriteProfile(TimelineProfileType& profile) override; | |
| #endif | |
| void NotifyAboutIsolateGroupShutdown(IsolateGroup* isolate_group) override; | |
| private: | |
| void WritePacket( | |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* packet); | |
| void DrainImpl(const TimelineEvent& event) final; | |
| void EmitModuleSymbolsFor(IsolateGroup* isolate_group); | |
| Mutex writer_mutex_; | |
| TracePacketWriter writer_; | |
| }; | |
| static TimelineEventRecorder* CreateTimelineEventPerfettoFileRecorder( | |
| const char* filename, | |
| bool intern_strings, | |
| const char** error) { | |
| void* file = TimelineEventFileRecorderBase::OpenTimelineFileForWriting( | |
| filename, error); | |
| if (file == nullptr) { | |
| return nullptr; | |
| } | |
| *error = nullptr; | |
| return new TimelineEventPerfettoFileRecorder(file, intern_strings); | |
| } | |
| #if defined(DART_INCLUDE_PROFILER) | |
| void TimelineEventPerfettoFileRecorder::WriteProfile( | |
| TimelineProfileType& profile) { | |
| // Profile conversion code checks for safepoints so we need to use safepoint | |
| // aware mutex locker here to avoid deadlocks when two threads call | |
| // |WriteProfile| and the third thread requests a safepoint. | |
| // | |
| // Note that Drain does not need this because it does not check for | |
| // safepoint. | |
| SafepointMutexLocker ml(&writer_mutex_); | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| profile.WritePerfetto( | |
| streaming_start_micros, streaming_stop_micros, | |
| writer_.interned_data_builder(), this, | |
| [](auto buffer, auto length, auto stream) { | |
| static_cast<TimelineEventPerfettoFileRecorder*>(stream)->Write( | |
| static_cast<const char*>(buffer), length); | |
| }); | |
| #else | |
| profile.PrintProfilePerfetto( | |
| writer_.interned_data_builder(), this, | |
| [](auto buffer, auto length, auto stream) { | |
| static_cast<TimelineEventPerfettoFileRecorder*>(stream)->Write( | |
| static_cast<const char*>(buffer), length); | |
| }); | |
| #endif | |
| } | |
| #endif | |
| TimelineEventPerfettoFileRecorder::TimelineEventPerfettoFileRecorder( | |
| void* file, | |
| bool intern_strings) | |
| : TimelineEventRecorderWithPerfettoSupport(file), | |
| writer_( | |
| packet(), | |
| [this](auto& packet) { this->WritePacket(&packet); }, | |
| intern_strings) { | |
| 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"); | |
| } | |
| void TimelineEventPerfettoFileRecorder::EmitModuleSymbolsFor( | |
| IsolateGroup* isolate_group) { | |
| ASSERT(IsolateGroup::Current() == nullptr); | |
| auto& interned_data_builder = writer_.interned_data_builder(); | |
| const auto build_id_iid = | |
| interned_data_builder.InternSyntheticBuildIdForIsolateGroup( | |
| isolate_group->id()); | |
| if (build_id_iid == 0) { | |
| return; | |
| } | |
| const bool kBypassSafepoint = false; | |
| Thread::EnterIsolateGroupAsHelper(isolate_group, Thread::kUnknownTask, | |
| kBypassSafepoint); | |
| { | |
| StackZone stack_zone(Thread::Current()); | |
| Code& code = Code::Handle(); | |
| GrowableArray<const Function*> functions; | |
| GrowableArray<TokenPosition> token_positions; | |
| // We assume that in general there is going to be a small number of mappings | |
| // (usually just one for each isolate group) and small number of isolate | |
| // groups (just one) so iterating them linearly is fine. | |
| for (auto interned_mapping : interned_data_builder.mappings()) { | |
| const auto mapping_iid = interned_mapping->iid; | |
| const auto& mapping = interned_mapping->data; | |
| if (mapping.build_id != build_id_iid) { | |
| continue; | |
| } | |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>& packet = | |
| this->packet(); | |
| perfetto::protos::pbzero::ModuleSymbols* module_symbols = nullptr; | |
| for (const auto& interned_frame : interned_data_builder.frames()) { | |
| const auto& frame = interned_frame->data; | |
| if (frame.mapping_iid != mapping_iid) { | |
| continue; | |
| } | |
| const auto pc = mapping.start + frame.rel_pc; | |
| // Note: PCs are already adjusted when converting from Sample to | |
| // interned Frame, see PerfettoPerfSampleWriter::WriteSample. | |
| code = | |
| ReversePc::Lookup(isolate_group, pc, /*is_return_address=*/false); | |
| if (code.IsNull()) { | |
| continue; | |
| } | |
| if (module_symbols == nullptr) { | |
| perfetto_utils::SetTrustedPacketSequenceId(packet.get()); | |
| module_symbols = packet->set_module_symbols(); | |
| const auto mapping_path = | |
| interned_data_builder.mapping_paths().GetByIid( | |
| mapping.path_string); | |
| module_symbols->set_path(mapping_path); | |
| module_symbols->set_build_id( | |
| interned_data_builder.build_ids().GetByIid(build_id_iid)); | |
| } | |
| auto* address_symbols = module_symbols->add_address_symbols(); | |
| address_symbols->set_address(frame.rel_pc); | |
| if (code.IsFunctionCode()) { | |
| const intptr_t offset = pc - code.PayloadStart(); | |
| code.GetInlinedFunctionsAtInstruction(offset, &functions, | |
| &token_positions); | |
| for (intptr_t i = functions.length() - 1; i >= 0; --i) { | |
| const char* function_name = | |
| functions[i]->QualifiedUserVisibleNameCString(); | |
| auto* line = address_symbols->add_lines(); | |
| line->set_function_name(function_name); | |
| } | |
| } else { | |
| auto* line = address_symbols->add_lines(); | |
| line->set_function_name(code.Name()); | |
| } | |
| } | |
| if (module_symbols != nullptr) { | |
| WritePacket(&packet); | |
| packet.Reset(); | |
| } | |
| } | |
| } | |
| Thread::ExitIsolateGroupAsHelper(kBypassSafepoint); | |
| } | |
| void TimelineEventPerfettoFileRecorder::NotifyAboutIsolateGroupShutdown( | |
| IsolateGroup* isolate_group) { | |
| SafepointMutexLocker ml(&writer_mutex_); | |
| EmitModuleSymbolsFor(isolate_group); | |
| } | |
| TimelineEventPerfettoFileRecorder::~TimelineEventPerfettoFileRecorder() { | |
| ShutDown(); | |
| #if defined(DART_PRECOMPILED_RUNTIME) && defined(DART_INCLUDE_PROFILER) | |
| if (!writer_.interned_data_builder().frames().IsEmpty()) { | |
| Isolate* caller_isolate = Isolate::Current(); | |
| if (caller_isolate != nullptr) { | |
| Thread::ExitIsolate(); | |
| } | |
| IsolateGroup::ForEach([&](auto group) { EmitModuleSymbolsFor(group); }); | |
| if (caller_isolate != nullptr) { | |
| Thread::EnterIsolate(caller_isolate); | |
| } | |
| } | |
| #endif | |
| // 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. | |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>& packet = | |
| this->packet(); | |
| 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) { | |
| { | |
| MutexLocker lock(&writer_mutex_); | |
| writer_.WriteEvent(event); | |
| } | |
| if (event.event_type() == TimelineEvent::kAsyncBegin || | |
| event.event_type() == TimelineEvent::kAsyncInstant) { | |
| AddAsyncTrackMetadataBasedOnEvent(event); | |
| } | |
| } | |
| #endif // defined(SUPPORT_PERFETTO) | |
| 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. | |
| OSThreadIterator it; | |
| MutexLocker ml(&lock_); | |
| Timeline::ReclaimCachedBlocksFromThreads(&it); | |
| 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; | |
| } | |
| bool Timeline::StreamTo(const char* recorder_kind, | |
| const char* filename, | |
| const char* streams, | |
| const char** error) { | |
| Timeline::Cleanup(); | |
| TimelineEventRecorder* recorder = nullptr; | |
| if (strcmp(recorder_kind, "perfettofile") == 0) { | |
| #if defined(SUPPORT_PERFETTO) | |
| recorder = CreateTimelineEventPerfettoFileRecorder( | |
| filename, /*intern_strings=*/true, error); | |
| if (recorder == nullptr) { | |
| return false; | |
| } | |
| #else | |
| *error = "Support for Perfetto recorder is not included into this build"; | |
| return false; | |
| #endif // defined(SUPPORT_PERFETTO) | |
| } else if (strcmp(recorder_kind, "file") == 0) { | |
| void* file = TimelineEventFileRecorderBase::OpenTimelineFileForWriting( | |
| filename, error); | |
| if (file == nullptr) { | |
| return false; | |
| } | |
| recorder = new TimelineEventFileRecorder(file); | |
| } else if (strcmp(recorder_kind, "systrace") == 0) { | |
| recorder = CreateSystraceTimelineRecorder(); | |
| if (recorder == nullptr) { | |
| *error = "Support for systrace recorder is not included into this build"; | |
| return false; | |
| } | |
| } | |
| Timeline::InitWithRecorder(recorder, streams); | |
| streaming_start_micros.store(OS::GetCurrentMonotonicMicrosForTimeline()); | |
| streaming_stop_micros.store(kMaxInt64); | |
| is_streaming_timeline.store(true); | |
| return true; | |
| } | |
| #if defined(DART_INCLUDE_PROFILER) | |
| void Timeline::DrainCompletedSampleBlocksIntoRecorder( | |
| NOT_IN_PRECOMPILED(Isolate* isolate)) { | |
| if (!is_streaming_timeline.load()) { | |
| return; | |
| } | |
| if (Profiler::sample_block_buffer() == nullptr) { | |
| return; | |
| } | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| auto& profile = *Profiler::sample_block_buffer(); | |
| #else | |
| auto thread = Thread::Current(); | |
| if (Isolate::IsSystemIsolate(isolate)) return; | |
| TIMELINE_DURATION(thread, Isolate, "Timeline::WriteProfile") | |
| DisableThreadInterruptsScope dtis(thread); | |
| StackZone zone(thread); | |
| Profile profile; | |
| NoAllocationSampleFilter filter(isolate->main_port(), Thread::kMutatorTask, | |
| streaming_start_micros, | |
| streaming_stop_micros); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| #endif | |
| RecorderSynchronizationLockScope ls; | |
| if (recorder_ != nullptr && ls.IsActive() && is_streaming_timeline.load()) { | |
| recorder_->WriteProfile(profile); | |
| } | |
| } | |
| #endif | |
| void Timeline::StopStreaming(bool reinitialize) { | |
| if (!is_streaming_timeline.load()) { | |
| return; | |
| } | |
| streaming_stop_micros.store(OS::GetCurrentMonotonicMicrosForTimeline()); | |
| is_streaming_timeline.store(false); | |
| Timeline::Cleanup(); | |
| if (reinitialize) { | |
| Timeline::Init(); | |
| } | |
| } | |
| void Timeline::NotifyAboutIsolateGroupShutdown(IsolateGroup* isolate_group) { | |
| #if defined(DART_INCLUDE_PROFILER) && defined(SUPPORT_PERFETTO) | |
| if (!is_streaming_timeline.load()) { | |
| return; | |
| } | |
| RecorderSynchronizationLockScope ls; | |
| if (recorder_ != nullptr && ls.IsActive()) { | |
| recorder_->NotifyAboutIsolateGroupShutdown(isolate_group); | |
| } | |
| #endif | |
| } | |
| 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) |