| // 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> |
| |
| #include "platform/atomic.h" |
| #include "platform/hashmap.h" |
| #include "vm/isolate.h" |
| #include "vm/json_stream.h" |
| #include "vm/lockers.h" |
| #include "vm/log.h" |
| #include "vm/object.h" |
| #include "vm/service.h" |
| #include "vm/service_event.h" |
| #include "vm/thread.h" |
| |
| #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) |
| #include "perfetto/ext/tracing/core/trace_packet.h" |
| #include "vm/perfetto_utils.h" |
| #include "vm/protos/perfetto/common/builtin_clock.pbzero.h" |
| #include "vm/protos/perfetto/trace/clock_snapshot.pbzero.h" |
| #include "vm/protos/perfetto/trace/trace_packet.pbzero.h" |
| #include "vm/protos/perfetto/trace/track_event/debug_annotation.pbzero.h" |
| #include "vm/protos/perfetto/trace/track_event/process_descriptor.pbzero.h" |
| #include "vm/protos/perfetto/trace/track_event/thread_descriptor.pbzero.h" |
| #include "vm/protos/perfetto/trace/track_event/track_descriptor.pbzero.h" |
| #include "vm/protos/perfetto/trace/track_event/track_event.pbzero.h" |
| #endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) |
| |
| namespace dart { |
| |
| #if defined(PRODUCT) |
| #define DEFAULT_TIMELINE_RECORDER "none" |
| #define SUPPORTED_TIMELINE_RECORDERS "systrace, file, callback" |
| #else |
| #define DEFAULT_TIMELINE_RECORDER "ring" |
| #if defined(SUPPORT_PERFETTO) |
| #define SUPPORTED_TIMELINE_RECORDERS \ |
| "ring, endless, startup, systrace, file, callback, perfettofile" |
| #else |
| #define SUPPORTED_TIMELINE_RECORDERS \ |
| "ring, endless, startup, systrace, file, callback" |
| #endif |
| #endif |
| |
| DEFINE_FLAG(bool, complete_timeline, false, "Record the complete timeline"); |
| DEFINE_FLAG(bool, startup_timeline, false, "Record the startup timeline"); |
| // TODO(derekx): Remove this flag in Dart 3.4. |
| DEFINE_FLAG( |
| bool, |
| systrace_timeline, |
| false, |
| "Record the timeline to the platform's tracing service if there is one"); |
| DEFINE_FLAG(bool, trace_timeline, false, "Trace timeline backend"); |
| DEFINE_FLAG(charp, |
| timeline_dir, |
| nullptr, |
| "Enable all timeline trace streams and output VM global trace " |
| "into specified directory. This flag is ignored by the file and " |
| "perfetto recorders."); |
| DEFINE_FLAG(charp, |
| timeline_streams, |
| nullptr, |
| "Comma separated list of timeline streams to record. " |
| "Valid values: all, API, Compiler, CompilerVerbose, Dart, " |
| "Debugger, Embedder, GC, Isolate, and VM."); |
| DEFINE_FLAG(charp, |
| timeline_recorder, |
| DEFAULT_TIMELINE_RECORDER, |
| "Select the timeline recorder used. " |
| "Valid values: none, " SUPPORTED_TIMELINE_RECORDERS) |
| |
| // 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* CreateTimelineRecorder() { |
| ASSERT(FLAG_timeline_recorder != nullptr); |
| const char* flag = FLAG_timeline_recorder; |
| |
| if (FLAG_systrace_timeline) { |
| OS::PrintErr( |
| "Warning: the --systrace-timeline flag is deprecated and will " |
| "be removed in Dart SDK v3.4. Please use --timeline-recorder=systrace " |
| "instead.\n"); |
| flag = "systrace"; |
| } else if (FLAG_timeline_dir != nullptr || FLAG_complete_timeline) { |
| // Some flags require that we use the endless recorder. |
| flag = "endless"; |
| } else if (FLAG_startup_timeline) { |
| flag = "startup"; |
| } |
| |
| if (strcmp("none", flag) == 0) { |
| return new TimelineEventNopRecorder(); |
| } |
| |
| // Systrace recorder. |
| if (strcmp("systrace", flag) == 0) { |
| #if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID) |
| return new TimelineEventSystraceRecorder(); |
| #elif defined(DART_HOST_OS_MACOS) |
| return new TimelineEventMacosRecorder(); |
| #elif defined(DART_HOST_OS_FUCHSIA) |
| return new TimelineEventFuchsiaRecorder(); |
| #else |
| // Not supported. A warning will be emitted below. |
| #endif |
| } |
| |
| if (Utils::StrStartsWith(flag, "file") && |
| (flag[4] == '\0' || flag[4] == ':' || flag[4] == '=')) { |
| const char* filename = flag[4] == '\0' ? "dart-timeline.json" : &flag[5]; |
| free(const_cast<char*>(FLAG_timeline_dir)); |
| FLAG_timeline_dir = nullptr; |
| return new TimelineEventFileRecorder(filename); |
| } |
| |
| if (strcmp("callback", flag) == 0) { |
| return new TimelineEventEmbedderCallbackRecorder(); |
| } |
| |
| #if !defined(PRODUCT) |
| #if defined(SUPPORT_PERFETTO) |
| // The Perfetto file recorder is disabled in PRODUCT mode to avoid the large |
| // binary size increase that it brings. |
| { |
| const intptr_t kPrefixLength = 12; |
| if (Utils::StrStartsWith(flag, "perfettofile") && |
| (flag[kPrefixLength] == '\0' || flag[kPrefixLength] == ':' || |
| flag[kPrefixLength] == '=')) { |
| const char* filename = flag[kPrefixLength] == '\0' |
| ? "dart.perfetto-trace" |
| : &flag[kPrefixLength + 1]; |
| free(const_cast<char*>(FLAG_timeline_dir)); |
| FLAG_timeline_dir = nullptr; |
| return new TimelineEventPerfettoFileRecorder(filename); |
| } |
| } |
| #endif // defined(SUPPORT_PERFETTO) |
| |
| // Recorders below do nothing useful in PRODUCT mode. You can't extract |
| // information available in them without vm-service. |
| if (strcmp("endless", flag) == 0) { |
| return new TimelineEventEndlessRecorder(); |
| } |
| |
| if (strcmp("startup", flag) == 0) { |
| return new TimelineEventStartupRecorder(); |
| } |
| |
| if (strcmp("ring", flag) == 0) { |
| return new TimelineEventRingRecorder(); |
| } |
| #endif // !defined(PRODUCT) |
| |
| if (strlen(flag) > 0 && strcmp(flag, DEFAULT_TIMELINE_RECORDER) != 0) { |
| OS::PrintErr( |
| "Warning: requested %s timeline recorder which is not supported, " |
| "defaulting to the " DEFAULT_TIMELINE_RECORDER " recorder\n", |
| flag); |
| } |
| |
| return CreateDefaultTimelineRecorder(); |
| } |
| |
| // Returns a caller freed array of stream names in FLAG_timeline_streams. |
| static MallocGrowableArray<char*>* GetEnabledByDefaultTimelineStreams() { |
| MallocGrowableArray<char*>* result = new MallocGrowableArray<char*>(); |
| if (FLAG_timeline_streams == nullptr) { |
| // Nothing set. |
| return result; |
| } |
| char* save_ptr; // Needed for strtok_r. |
| // strtok modifies arg 1 so we make a copy of it. |
| char* streams = Utils::StrDup(FLAG_timeline_streams); |
| char* token = strtok_r(streams, ",", &save_ptr); |
| while (token != nullptr) { |
| result->Add(Utils::StrDup(token)); |
| token = strtok_r(nullptr, ",", &save_ptr); |
| } |
| free(streams); |
| return result; |
| } |
| |
| // Frees the result of |GetEnabledByDefaultTimelineStreams|. |
| static void FreeEnabledByDefaultTimelineStreams( |
| MallocGrowableArray<char*>* streams) { |
| if (streams == nullptr) { |
| return; |
| } |
| for (intptr_t i = 0; i < streams->length(); i++) { |
| free((*streams)[i]); |
| } |
| delete streams; |
| } |
| |
| // Returns true if |streams| contains |stream| or "all". Not case sensitive. |
| static bool HasStream(MallocGrowableArray<char*>* streams, const char* stream) { |
| if ((FLAG_timeline_dir != nullptr) || FLAG_complete_timeline || |
| FLAG_startup_timeline) { |
| return true; |
| } |
| for (intptr_t i = 0; i < streams->length(); i++) { |
| const char* checked_stream = (*streams)[i]; |
| if ((strstr(checked_stream, "all") != nullptr) || |
| (strstr(checked_stream, stream) != nullptr)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void Timeline::Init() { |
| ASSERT(recorder_ == nullptr); |
| recorder_ = CreateTimelineRecorder(); |
| |
| RecorderSynchronizationLock::Init(); |
| |
| // The following is needed to backfill information about any |OSThread|s that |
| // were initialized before this point. |
| OSThreadIterator it; |
| while (it.HasNext()) { |
| OSThread& thread = *it.Next(); |
| recorder_->AddTrackMetadataBasedOnThread( |
| OS::ProcessId(), OSThread::ThreadIdToIntPtr(thread.trace_id()), |
| thread.name()); |
| } |
| if (FLAG_trace_timeline) { |
| OS::PrintErr("Using the %s timeline recorder.\n", recorder_->name()); |
| } |
| ASSERT(recorder_ != nullptr); |
| enabled_streams_ = GetEnabledByDefaultTimelineStreams(); |
| // Global overrides. |
| #define TIMELINE_STREAM_FLAG_DEFAULT(name, ...) \ |
| stream_##name##_.set_enabled(HasStream(enabled_streams_, #name)); |
| TIMELINE_STREAM_LIST(TIMELINE_STREAM_FLAG_DEFAULT) |
| #undef TIMELINE_STREAM_FLAG_DEFAULT |
| } |
| |
| void Timeline::Cleanup() { |
| ASSERT(recorder_ != nullptr); |
| |
| #ifndef PRODUCT |
| if (FLAG_timeline_dir != nullptr) { |
| recorder_->WriteTo(FLAG_timeline_dir); |
| } |
| #endif |
| |
| // Disable global streams. |
| #define TIMELINE_STREAM_DISABLE(name, ...) \ |
| Timeline::stream_##name##_.set_enabled(false); |
| TIMELINE_STREAM_LIST(TIMELINE_STREAM_DISABLE) |
| #undef TIMELINE_STREAM_DISABLE |
| RecorderSynchronizationLock::WaitForShutdown(); |
| Timeline::Clear(); |
| delete recorder_; |
| recorder_ = nullptr; |
| if (enabled_streams_ != nullptr) { |
| FreeEnabledByDefaultTimelineStreams(enabled_streams_); |
| enabled_streams_ = nullptr; |
| } |
| } |
| |
| void Timeline::ReclaimCachedBlocksFromThreads() { |
| RecorderSynchronizationLockScope ls; |
| TimelineEventRecorder* recorder = Timeline::recorder(); |
| if (recorder == nullptr || ls.IsUninitialized()) { |
| return; |
| } |
| ASSERT(recorder != nullptr); |
| // Iterate over threads. |
| OSThreadIterator it; |
| while (it.HasNext()) { |
| OSThread* thread = it.Next(); |
| MutexLocker ml(thread->timeline_block_lock()); |
| // Grab block and clear it. |
| TimelineEventBlock* block = thread->TimelineBlockLocked(); |
| thread->SetTimelineBlockLocked(nullptr); |
| recorder->FinishBlock(block); |
| } |
| } |
| |
| #ifndef PRODUCT |
| void Timeline::PrintFlagsToJSONArray(JSONArray* arr) { |
| #define ADD_RECORDED_STREAM_NAME(name, ...) \ |
| if (stream_##name##_.enabled()) { \ |
| arr->AddValue(#name); \ |
| } |
| TIMELINE_STREAM_LIST(ADD_RECORDED_STREAM_NAME); |
| #undef ADD_RECORDED_STREAM_NAME |
| } |
| |
| void Timeline::PrintFlagsToJSON(JSONStream* js) { |
| JSONObject obj(js); |
| obj.AddProperty("type", "TimelineFlags"); |
| RecorderSynchronizationLockScope ls; |
| TimelineEventRecorder* recorder = Timeline::recorder(); |
| if (recorder == nullptr || !ls.IsActive()) { |
| obj.AddProperty("recorderName", "null"); |
| } else { |
| obj.AddProperty("recorderName", recorder->name()); |
| } |
| { |
| JSONArray availableStreams(&obj, "availableStreams"); |
| #define ADD_STREAM_NAME(name, ...) availableStreams.AddValue(#name); |
| TIMELINE_STREAM_LIST(ADD_STREAM_NAME); |
| #undef ADD_STREAM_NAME |
| } |
| { |
| JSONArray recordedStreams(&obj, "recordedStreams"); |
| #define ADD_RECORDED_STREAM_NAME(name, ...) \ |
| if (stream_##name##_.enabled()) { \ |
| recordedStreams.AddValue(#name); \ |
| } |
| TIMELINE_STREAM_LIST(ADD_RECORDED_STREAM_NAME); |
| #undef ADD_RECORDED_STREAM_NAME |
| } |
| } |
| #endif |
| |
| void Timeline::Clear() { |
| RecorderSynchronizationLockScope ls; |
| TimelineEventRecorder* recorder = Timeline::recorder(); |
| if (recorder == nullptr || ls.IsUninitialized()) { |
| return; |
| } |
| ASSERT(recorder != nullptr); |
| // Acquire the recorder's lock to prevent the reclaimed blocks from being |
| // handed out again until they have been cleared. |
| MutexLocker ml(&recorder->lock_); |
| ReclaimCachedBlocksFromThreads(); |
| recorder->ClearLocked(); |
| } |
| |
| void TimelineEventArguments::SetNumArguments(intptr_t length) { |
| if (length == length_) { |
| return; |
| } |
| if (length == 0) { |
| Free(); |
| return; |
| } |
| if (buffer_ == nullptr) { |
| // calloc already nullifies |
| buffer_ = reinterpret_cast<TimelineEventArgument*>( |
| calloc(sizeof(TimelineEventArgument), length)); |
| } else { |
| for (intptr_t i = length; i < length_; ++i) { |
| free(buffer_[i].value); |
| } |
| buffer_ = reinterpret_cast<TimelineEventArgument*>( |
| realloc(buffer_, sizeof(TimelineEventArgument) * length)); |
| if (length > length_) { |
| memset(buffer_ + length_, 0, |
| sizeof(TimelineEventArgument) * (length - length_)); |
| } |
| } |
| length_ = length; |
| } |
| |
| void TimelineEventArguments::SetArgument(intptr_t i, |
| const char* name, |
| char* argument) { |
| ASSERT(i >= 0); |
| ASSERT(i < length_); |
| buffer_[i].name = name; |
| buffer_[i].value = argument; |
| } |
| |
| void TimelineEventArguments::CopyArgument(intptr_t i, |
| const char* name, |
| const char* argument) { |
| SetArgument(i, name, Utils::StrDup(argument)); |
| } |
| |
| void TimelineEventArguments::FormatArgument(intptr_t i, |
| const char* name, |
| const char* fmt, |
| va_list args) { |
| ASSERT(i >= 0); |
| ASSERT(i < length_); |
| va_list measure_args; |
| va_copy(measure_args, args); |
| intptr_t len = Utils::VSNPrint(nullptr, 0, fmt, measure_args); |
| va_end(measure_args); |
| |
| char* buffer = reinterpret_cast<char*>(malloc(len + 1)); |
| va_list print_args; |
| va_copy(print_args, args); |
| Utils::VSNPrint(buffer, (len + 1), fmt, print_args); |
| va_end(print_args); |
| |
| SetArgument(i, name, buffer); |
| } |
| |
| void TimelineEventArguments::StealArguments(TimelineEventArguments* arguments) { |
| Free(); |
| length_ = arguments->length_; |
| buffer_ = arguments->buffer_; |
| arguments->length_ = 0; |
| arguments->buffer_ = nullptr; |
| } |
| |
| void TimelineEventArguments::Free() { |
| if (buffer_ == nullptr) { |
| return; |
| } |
| for (intptr_t i = 0; i < length_; i++) { |
| free(buffer_[i].value); |
| } |
| free(buffer_); |
| buffer_ = nullptr; |
| length_ = 0; |
| } |
| |
| TimelineEventRecorder* Timeline::recorder_ = nullptr; |
| Dart_TimelineRecorderCallback Timeline::callback_ = nullptr; |
| MallocGrowableArray<char*>* Timeline::enabled_streams_ = nullptr; |
| bool Timeline::recorder_discards_clock_values_ = false; |
| |
| #define TIMELINE_STREAM_DEFINE(name, fuchsia_name, static_labels) \ |
| TimelineStream Timeline::stream_##name##_(#name, fuchsia_name, \ |
| static_labels, false); |
| TIMELINE_STREAM_LIST(TIMELINE_STREAM_DEFINE) |
| #undef TIMELINE_STREAM_DEFINE |
| |
| TimelineEvent::TimelineEvent() |
| : timestamp0_(0), |
| timestamp1_or_id_(0), |
| flow_id_count_(0), |
| flow_ids_(), |
| state_(0), |
| label_(nullptr), |
| stream_(nullptr), |
| thread_(OSThread::kInvalidThreadId), |
| isolate_id_(ILLEGAL_ISOLATE_ID), |
| isolate_group_id_(ILLEGAL_ISOLATE_GROUP_ID) {} |
| |
| TimelineEvent::~TimelineEvent() { |
| Reset(); |
| } |
| |
| void TimelineEvent::Reset() { |
| timestamp0_ = 0; |
| timestamp1_or_id_ = 0; |
| flow_id_count_ = 0; |
| flow_ids_.reset(); |
| if (owns_label() && label_ != nullptr) { |
| free(const_cast<char*>(label_)); |
| } |
| label_ = nullptr; |
| stream_ = nullptr; |
| thread_ = OSThread::kInvalidThreadId; |
| isolate_id_ = ILLEGAL_ISOLATE_ID; |
| isolate_group_id_ = ILLEGAL_ISOLATE_GROUP_ID; |
| arguments_.Free(); |
| state_ = 0; |
| } |
| |
| void TimelineEvent::AsyncBegin(const char* label, |
| int64_t async_id, |
| int64_t micros) { |
| Init(kAsyncBegin, label); |
| set_timestamp0(micros); |
| // Overload timestamp1_ with the async_id. |
| set_timestamp1_or_id(async_id); |
| } |
| |
| void TimelineEvent::AsyncInstant(const char* label, |
| int64_t async_id, |
| int64_t micros) { |
| Init(kAsyncInstant, label); |
| set_timestamp0(micros); |
| // Overload timestamp1_ with the async_id. |
| set_timestamp1_or_id(async_id); |
| } |
| |
| void TimelineEvent::AsyncEnd(const char* label, |
| int64_t async_id, |
| int64_t micros) { |
| Init(kAsyncEnd, label); |
| set_timestamp0(micros); |
| // Overload timestamp1_ with the async_id. |
| set_timestamp1_or_id(async_id); |
| } |
| |
| void TimelineEvent::DurationBegin(const char* label, int64_t micros) { |
| Init(kDuration, label); |
| set_timestamp0(micros); |
| } |
| |
| void TimelineEvent::Instant(const char* label, int64_t micros) { |
| Init(kInstant, label); |
| set_timestamp0(micros); |
| } |
| |
| void TimelineEvent::Duration(const char* label, |
| int64_t start_micros, |
| int64_t end_micros) { |
| Init(kDuration, label); |
| set_timestamp0(start_micros); |
| set_timestamp1_or_id(end_micros); |
| } |
| |
| void TimelineEvent::Begin(const char* label, int64_t id, int64_t micros) { |
| Init(kBegin, label); |
| set_timestamp0(micros); |
| // Overload timestamp1_ with the event ID. This is required for the MacOS |
| // recorder to work. |
| set_timestamp1_or_id(id); |
| } |
| |
| void TimelineEvent::End(const char* label, int64_t id, int64_t micros) { |
| Init(kEnd, label); |
| set_timestamp0(micros); |
| // Overload timestamp1_ with the event ID. This is required for the MacOS |
| // recorder to work. |
| set_timestamp1_or_id(id); |
| } |
| |
| void TimelineEvent::Counter(const char* label, int64_t micros) { |
| Init(kCounter, label); |
| set_timestamp0(micros); |
| } |
| |
| void TimelineEvent::FlowBegin(const char* label, int64_t id, int64_t micros) { |
| Init(kFlowBegin, label); |
| set_timestamp0(micros); |
| // Overload timestamp1_ with the flow ID. |
| set_timestamp1_or_id(id); |
| } |
| |
| void TimelineEvent::FlowStep(const char* label, int64_t id, int64_t micros) { |
| Init(kFlowStep, label); |
| set_timestamp0(micros); |
| // Overload timestamp1_ with the flow ID. |
| set_timestamp1_or_id(id); |
| } |
| |
| void TimelineEvent::FlowEnd(const char* label, int64_t id, int64_t micros) { |
| Init(kFlowEnd, label); |
| set_timestamp0(micros); |
| // Overload timestamp1_ with the flow ID. |
| set_timestamp1_or_id(id); |
| } |
| |
| void TimelineEvent::Metadata(const char* label, int64_t micros) { |
| Init(kMetadata, label); |
| set_timestamp0(micros); |
| } |
| |
| void TimelineEvent::CompleteWithPreSerializedArgs(char* args_json) { |
| set_pre_serialized_args(true); |
| SetNumArguments(1); |
| SetArgument(0, "Dart Arguments", args_json); |
| Complete(); |
| } |
| |
| void TimelineEvent::FormatArgument(intptr_t i, |
| const char* name, |
| const char* fmt, |
| ...) { |
| va_list args; |
| va_start(args, fmt); |
| arguments_.FormatArgument(i, name, fmt, args); |
| va_end(args); |
| } |
| |
| void TimelineEvent::Complete() { |
| TimelineEventRecorder* recorder = Timeline::recorder(); |
| recorder->CompleteEvent(this); |
| // Paired with |RecorderSynchronizationLock::EnterLock()| in |
| // |TimelineStream::StartEvent()|. |
| RecorderSynchronizationLock::ExitLock(); |
| } |
| |
| void TimelineEvent::Init(EventType event_type, const char* label) { |
| ASSERT(label != nullptr); |
| state_ = 0; |
| timestamp0_ = 0; |
| timestamp1_or_id_ = 0; |
| flow_id_count_ = 0; |
| flow_ids_.reset(); |
| OSThread* os_thread = OSThread::Current(); |
| ASSERT(os_thread != nullptr); |
| thread_ = os_thread->trace_id(); |
| auto thread = Thread::Current(); |
| auto isolate = thread != nullptr ? thread->isolate() : nullptr; |
| auto isolate_group = thread != nullptr ? thread->isolate_group() : nullptr; |
| isolate_id_ = (isolate != nullptr) ? isolate->main_port() : ILLEGAL_PORT; |
| isolate_group_id_ = (isolate_group != nullptr) ? isolate_group->id() : 0; |
| isolate_data_ = |
| (isolate != nullptr) ? isolate->init_callback_data() : nullptr; |
| isolate_group_data_ = |
| (isolate_group != nullptr) ? isolate_group->embedder_data() : nullptr; |
| label_ = label; |
| arguments_.Free(); |
| set_event_type(event_type); |
| set_pre_serialized_args(false); |
| set_owns_label(false); |
| } |
| |
| bool TimelineEvent::Within(int64_t time_origin_micros, |
| int64_t time_extent_micros) { |
| if ((time_origin_micros == -1) || (time_extent_micros == -1)) { |
| // No time range specified. |
| return true; |
| } |
| if (IsFinishedDuration()) { |
| // Event is from e_t0 to e_t1. |
| int64_t e_t0 = TimeOrigin(); |
| int64_t e_t1 = TimeEnd(); |
| ASSERT(e_t0 <= e_t1); |
| // Range is from r_t0 to r_t1. |
| int64_t r_t0 = time_origin_micros; |
| int64_t r_t1 = time_origin_micros + time_extent_micros; |
| ASSERT(r_t0 <= r_t1); |
| return !((r_t1 < e_t0) || (e_t1 < r_t0)); |
| } |
| int64_t delta = TimeOrigin() - time_origin_micros; |
| return (delta >= 0) && (delta <= time_extent_micros); |
| } |
| |
| #ifndef PRODUCT |
| void TimelineEvent::PrintJSON(JSONStream* stream) const { |
| PrintJSON(stream->writer()); |
| } |
| #endif |
| |
| void TimelineEvent::PrintJSON(JSONWriter* writer) const { |
| writer->OpenObject(); |
| int64_t pid = OS::ProcessId(); |
| int64_t tid = OSThread::ThreadIdToIntPtr(thread_); |
| writer->PrintProperty("name", label_); |
| writer->PrintProperty("cat", stream_ != nullptr ? stream_->name() : nullptr); |
| writer->PrintProperty64("tid", tid); |
| writer->PrintProperty64("pid", pid); |
| writer->PrintProperty64("ts", TimeOrigin()); |
| switch (event_type()) { |
| case kBegin: { |
| writer->PrintProperty("ph", "B"); |
| } break; |
| case kEnd: { |
| writer->PrintProperty("ph", "E"); |
| } break; |
| case kDuration: { |
| writer->PrintProperty("ph", "X"); |
| writer->PrintProperty64("dur", TimeDuration()); |
| } break; |
| case kInstant: { |
| writer->PrintProperty("ph", "i"); |
| writer->PrintProperty("s", "p"); |
| } break; |
| case kAsyncBegin: { |
| writer->PrintProperty("ph", "b"); |
| writer->PrintfProperty("id", "%" Px64 "", Id()); |
| } break; |
| case kAsyncInstant: { |
| writer->PrintProperty("ph", "n"); |
| writer->PrintfProperty("id", "%" Px64 "", Id()); |
| } break; |
| case kAsyncEnd: { |
| writer->PrintProperty("ph", "e"); |
| writer->PrintfProperty("id", "%" Px64 "", Id()); |
| } break; |
| case kCounter: { |
| writer->PrintProperty("ph", "C"); |
| } break; |
| case kFlowBegin: { |
| writer->PrintProperty("ph", "s"); |
| writer->PrintfProperty("id", "%" Px64 "", Id()); |
| } break; |
| case kFlowStep: { |
| writer->PrintProperty("ph", "t"); |
| writer->PrintfProperty("id", "%" Px64 "", Id()); |
| } break; |
| case kFlowEnd: { |
| writer->PrintProperty("ph", "f"); |
| writer->PrintProperty("bp", "e"); |
| writer->PrintfProperty("id", "%" Px64 "", Id()); |
| } break; |
| case kMetadata: { |
| writer->PrintProperty("ph", "M"); |
| } break; |
| default: |
| UNIMPLEMENTED(); |
| } |
| |
| if (ArgsArePreSerialized()) { |
| ASSERT(arguments_.length() == 1); |
| writer->AppendSerializedObject("args", arguments_[0].value); |
| if (HasIsolateId()) { |
| writer->UncloseObject(); |
| writer->PrintfProperty("isolateId", ISOLATE_SERVICE_ID_FORMAT_STRING, |
| static_cast<int64_t>(isolate_id_)); |
| writer->CloseObject(); |
| } |
| if (HasIsolateGroupId()) { |
| writer->UncloseObject(); |
| writer->PrintfProperty("isolateGroupId", |
| ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING, |
| isolate_group_id_); |
| writer->CloseObject(); |
| } else { |
| ASSERT(isolate_group_id_ == ILLEGAL_PORT); |
| } |
| } else { |
| writer->OpenObject("args"); |
| for (intptr_t i = 0; i < arguments_.length(); i++) { |
| const TimelineEventArgument& arg = arguments_[i]; |
| writer->PrintProperty(arg.name, arg.value); |
| } |
| if (HasIsolateId()) { |
| writer->PrintfProperty("isolateId", ISOLATE_SERVICE_ID_FORMAT_STRING, |
| static_cast<int64_t>(isolate_id_)); |
| } |
| if (HasIsolateGroupId()) { |
| writer->PrintfProperty("isolateGroupId", |
| ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING, |
| isolate_group_id_); |
| } else { |
| ASSERT(isolate_group_id_ == ILLEGAL_PORT); |
| } |
| writer->CloseObject(); |
| } |
| writer->CloseObject(); |
| } |
| |
| #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) |
| inline void AddSyncEventFields( |
| perfetto::protos::pbzero::TrackEvent* track_event, |
| const TimelineEvent& event) { |
| track_event->set_track_uuid(OSThread::ThreadIdToIntPtr(event.thread())); |
| } |
| |
| inline void AddAsyncEventFields( |
| perfetto::protos::pbzero::TrackEvent* track_event, |
| const TimelineEvent& event) { |
| track_event->set_track_uuid(event.Id()); |
| } |
| |
| inline void AddBeginAndInstantEventCommonFields( |
| perfetto::protos::pbzero::TrackEvent* track_event, |
| const TimelineEvent& event) { |
| track_event->set_name(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]); |
| } |
| } |
| |
| inline void AddBeginEventFields( |
| perfetto::protos::pbzero::TrackEvent* track_event, |
| const TimelineEvent& event) { |
| AddBeginAndInstantEventCommonFields(track_event, event); |
| track_event->set_type( |
| perfetto::protos::pbzero::TrackEvent::Type::TYPE_SLICE_BEGIN); |
| } |
| |
| inline void AddInstantEventFields( |
| perfetto::protos::pbzero::TrackEvent* track_event, |
| const TimelineEvent& event) { |
| AddBeginAndInstantEventCommonFields(track_event, event); |
| track_event->set_type( |
| perfetto::protos::pbzero::TrackEvent::Type::TYPE_INSTANT); |
| } |
| |
| inline void AddEndEventFields( |
| perfetto::protos::pbzero::TrackEvent* track_event) { |
| track_event->set_type( |
| perfetto::protos::pbzero::TrackEvent::Type::TYPE_SLICE_END); |
| } |
| |
| inline void AddDebugAnnotations( |
| perfetto::protos::pbzero::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(); |
| debug_annotation.set_name(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(); |
| debug_annotation.set_name(event.arguments()[i].name); |
| debug_annotation.set_string_value(event.arguments()[i].value); |
| } |
| } |
| } |
| if (event.HasIsolateId()) { |
| perfetto::protos::pbzero::DebugAnnotation& debug_annotation = |
| *track_event->add_debug_annotations(); |
| debug_annotation.set_name("isolateId"); |
| std::unique_ptr<const char[]> formatted_isolate_id = |
| event.GetFormattedIsolateId(); |
| debug_annotation.set_string_value(formatted_isolate_id.get()); |
| } |
| if (event.HasIsolateGroupId()) { |
| perfetto::protos::pbzero::DebugAnnotation& debug_annotation = |
| *track_event->add_debug_annotations(); |
| debug_annotation.set_name("isolateGroupId"); |
| std::unique_ptr<const char[]> formatted_isolate_group = |
| event.GetFormattedIsolateGroupId(); |
| debug_annotation.set_string_value(formatted_isolate_group.get()); |
| } |
| } |
| |
| bool TimelineEvent::CanBeRepresentedByPerfettoTracePacket() const { |
| switch (event_type()) { |
| case TimelineEvent::kBegin: |
| case TimelineEvent::kEnd: |
| case TimelineEvent::kDuration: |
| case TimelineEvent::kInstant: |
| case TimelineEvent::kAsyncBegin: |
| case TimelineEvent::kAsyncEnd: |
| case TimelineEvent::kAsyncInstant: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| void TimelineEvent::PopulateTracePacket( |
| perfetto::protos::pbzero::TracePacket* packet) const { |
| ASSERT(packet != nullptr); |
| ASSERT(CanBeRepresentedByPerfettoTracePacket()); |
| |
| perfetto_utils::SetTrustedPacketSequenceId(packet); |
| perfetto_utils::SetTimestampAndMonotonicClockId(packet, TimeOrigin()); |
| perfetto::protos::pbzero::TrackEvent* track_event = packet->set_track_event(); |
| track_event->add_categories(stream()->name()); |
| |
| const TimelineEvent& event = *this; |
| switch (event_type()) { |
| case TimelineEvent::kBegin: { |
| AddSyncEventFields(track_event, event); |
| AddBeginEventFields(track_event, event); |
| break; |
| } |
| case TimelineEvent::kEnd: { |
| AddSyncEventFields(track_event, event); |
| AddEndEventFields(track_event); |
| break; |
| } |
| case TimelineEvent::kInstant: { |
| AddSyncEventFields(track_event, event); |
| AddInstantEventFields(track_event, event); |
| break; |
| } |
| case TimelineEvent::kAsyncBegin: { |
| AddAsyncEventFields(track_event, event); |
| AddBeginEventFields(track_event, event); |
| break; |
| } |
| case TimelineEvent::kAsyncEnd: { |
| AddAsyncEventFields(track_event, event); |
| AddEndEventFields(track_event); |
| break; |
| } |
| case TimelineEvent::kAsyncInstant: { |
| AddAsyncEventFields(track_event, event); |
| AddInstantEventFields(track_event, event); |
| break; |
| } |
| default: |
| break; |
| } |
| AddDebugAnnotations(track_event, event); |
| } |
| #endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) |
| |
| int64_t TimelineEvent::LowTime() const { |
| return timestamp0_; |
| } |
| |
| int64_t TimelineEvent::HighTime() const { |
| if (event_type() == kDuration) { |
| return timestamp1_or_id_; |
| } else { |
| return timestamp0_; |
| } |
| } |
| |
| int64_t TimelineEvent::TimeDuration() const { |
| ASSERT(event_type() == kDuration); |
| if (timestamp1_or_id_ == 0) { |
| // This duration is still open, use current time as end. |
| return OS::GetCurrentMonotonicMicrosForTimeline() - timestamp0_; |
| } |
| return timestamp1_or_id_ - timestamp0_; |
| } |
| |
| bool TimelineEvent::HasIsolateId() const { |
| return isolate_id_ != ILLEGAL_ISOLATE_ID; |
| } |
| |
| bool TimelineEvent::HasIsolateGroupId() const { |
| return isolate_group_id_ != ILLEGAL_ISOLATE_GROUP_ID; |
| } |
| |
| std::unique_ptr<const char[]> TimelineEvent::GetFormattedIsolateId() const { |
| ASSERT(HasIsolateId()); |
| intptr_t formatted_isolate_id_buffer_size = |
| Utils::SNPrint(nullptr, 0, ISOLATE_SERVICE_ID_FORMAT_STRING, |
| isolate_id_) + |
| 1; |
| auto formatted_isolate_id = |
| std::make_unique<char[]>(formatted_isolate_id_buffer_size); |
| Utils::SNPrint(formatted_isolate_id.get(), formatted_isolate_id_buffer_size, |
| ISOLATE_SERVICE_ID_FORMAT_STRING, isolate_id_); |
| return formatted_isolate_id; |
| } |
| |
| std::unique_ptr<const char[]> TimelineEvent::GetFormattedIsolateGroupId() |
| const { |
| ASSERT(HasIsolateGroupId()); |
| intptr_t formatted_isolate_group_id_buffer_size = |
| Utils::SNPrint(nullptr, 0, ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING, |
| isolate_group_id_) + |
| 1; |
| auto formatted_isolate_group_id = |
| std::make_unique<char[]>(formatted_isolate_group_id_buffer_size); |
| Utils::SNPrint(formatted_isolate_group_id.get(), |
| formatted_isolate_group_id_buffer_size, |
| ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING, isolate_group_id_); |
| return formatted_isolate_group_id; |
| } |
| |
| TimelineTrackMetadata::TimelineTrackMetadata(intptr_t pid, |
| intptr_t tid, |
| CStringUniquePtr&& track_name) |
| : pid_(pid), tid_(tid), track_name_(std::move(track_name)) {} |
| |
| void TimelineTrackMetadata::set_track_name(CStringUniquePtr&& track_name) { |
| track_name_ = std::move(track_name); |
| } |
| |
| #if !defined(PRODUCT) |
| void TimelineTrackMetadata::PrintJSON(const JSONArray& jsarr_events) const { |
| JSONObject jsobj(&jsarr_events); |
| jsobj.AddProperty("name", "thread_name"); |
| jsobj.AddProperty("ph", "M"); |
| jsobj.AddProperty("pid", pid()); |
| jsobj.AddProperty("tid", tid()); |
| { |
| JSONObject jsobj_args(&jsobj, "args"); |
| jsobj_args.AddPropertyF("name", "%s (%" Pd ")", track_name(), tid()); |
| jsobj_args.AddProperty("mode", "basic"); |
| } |
| } |
| |
| #if defined(SUPPORT_PERFETTO) |
| void TimelineTrackMetadata::PopulateTracePacket( |
| perfetto::protos::pbzero::TracePacket* track_descriptor_packet) const { |
| perfetto_utils::SetTrustedPacketSequenceId(track_descriptor_packet); |
| |
| perfetto::protos::pbzero::TrackDescriptor& track_descriptor = |
| *track_descriptor_packet->set_track_descriptor(); |
| track_descriptor.set_parent_uuid(pid()); |
| track_descriptor.set_uuid(tid()); |
| |
| perfetto::protos::pbzero::ThreadDescriptor& thread_descriptor = |
| *track_descriptor.set_thread(); |
| thread_descriptor.set_pid(pid()); |
| thread_descriptor.set_tid(tid()); |
| thread_descriptor.set_thread_name(track_name()); |
| } |
| #endif // defined(SUPPORT_PERFETTO) |
| #endif // !defined(PRODUCT) |
| |
| AsyncTimelineTrackMetadata::AsyncTimelineTrackMetadata(intptr_t pid, |
| intptr_t async_id) |
| : pid_(pid), async_id_(async_id) {} |
| |
| #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) |
| void AsyncTimelineTrackMetadata::PopulateTracePacket( |
| perfetto::protos::pbzero::TracePacket* track_descriptor_packet) const { |
| perfetto_utils::SetTrustedPacketSequenceId(track_descriptor_packet); |
| perfetto::protos::pbzero::TrackDescriptor& track_descriptor = |
| *track_descriptor_packet->set_track_descriptor(); |
| track_descriptor.set_parent_uuid(pid()); |
| track_descriptor.set_uuid(async_id()); |
| } |
| #endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) |
| |
| TimelineStream::TimelineStream(const char* name, |
| const char* fuchsia_name, |
| bool has_static_labels, |
| bool enabled) |
| : name_(name), |
| fuchsia_name_(fuchsia_name), |
| #if defined(DART_HOST_OS_FUCHSIA) |
| enabled_(static_cast<uintptr_t>(true)) // For generated code. |
| #else |
| enabled_(static_cast<uintptr_t>(enabled)) |
| #endif |
| { |
| #if defined(DART_HOST_OS_MACOS) |
| macos_log_ = os_log_create("Dart", name); |
| has_static_labels_ = has_static_labels; |
| #endif |
| } |
| |
| TimelineEvent* TimelineStream::StartEvent() { |
| // Paired with |RecorderSynchronizationLock::ExitLock()| in |
| // |TimelineEvent::Complete()|. |
| // |
| // The lock must be held until the event is completed to avoid having the |
| // memory backing the event being freed in the middle of processing the |
| // event. |
| RecorderSynchronizationLock::EnterLock(); |
| TimelineEventRecorder* recorder = Timeline::recorder(); |
| if (!enabled() || (recorder == nullptr) || |
| !RecorderSynchronizationLock::IsActive()) { |
| RecorderSynchronizationLock::ExitLock(); |
| return nullptr; |
| } |
| ASSERT(name_ != nullptr); |
| TimelineEvent* event = recorder->StartEvent(); |
| if (event == nullptr) { |
| RecorderSynchronizationLock::ExitLock(); |
| return nullptr; |
| } |
| event->StreamInit(this); |
| return event; |
| } |
| |
| TimelineEventScope::TimelineEventScope(TimelineStream* stream, |
| const char* label) |
| : StackResource(static_cast<Thread*>(nullptr)), |
| stream_(stream), |
| label_(label), |
| enabled_(false) { |
| Init(); |
| } |
| |
| TimelineEventScope::TimelineEventScope(Thread* thread, |
| TimelineStream* stream, |
| const char* label) |
| : StackResource(thread), stream_(stream), label_(label), enabled_(false) { |
| Init(); |
| } |
| |
| TimelineEventScope::~TimelineEventScope() {} |
| |
| void TimelineEventScope::Init() { |
| ASSERT(enabled_ == false); |
| ASSERT(label_ != nullptr); |
| ASSERT(stream_ != nullptr); |
| if (!stream_->enabled()) { |
| // Stream is not enabled, do nothing. |
| return; |
| } |
| enabled_ = true; |
| Thread* thread = static_cast<Thread*>(this->thread()); |
| if (thread != nullptr) { |
| id_ = thread->GetNextTaskId(); |
| } else { |
| static RelaxedAtomic<int64_t> next_bootstrap_task_id = {0}; |
| id_ = next_bootstrap_task_id.fetch_add(1); |
| } |
| } |
| |
| void TimelineEventScope::SetNumArguments(intptr_t length) { |
| if (!enabled()) { |
| return; |
| } |
| arguments_.SetNumArguments(length); |
| } |
| |
| // |name| must be a compile time constant. Takes ownership of |argumentp|. |
| void TimelineEventScope::SetArgument(intptr_t i, |
| const char* name, |
| char* argument) { |
| if (!enabled()) { |
| return; |
| } |
| arguments_.SetArgument(i, name, argument); |
| } |
| |
| // |name| must be a compile time constant. Copies |argument|. |
| void TimelineEventScope::CopyArgument(intptr_t i, |
| const char* name, |
| const char* argument) { |
| if (!enabled()) { |
| return; |
| } |
| arguments_.CopyArgument(i, name, argument); |
| } |
| |
| void TimelineEventScope::FormatArgument(intptr_t i, |
| const char* name, |
| const char* fmt, |
| ...) { |
| if (!enabled()) { |
| return; |
| } |
| va_list args; |
| va_start(args, fmt); |
| arguments_.FormatArgument(i, name, fmt, args); |
| va_end(args); |
| } |
| |
| void TimelineEventScope::StealArguments(TimelineEvent* event) { |
| if (event == nullptr) { |
| return; |
| } |
| event->StealArguments(&arguments_); |
| } |
| |
| TimelineBeginEndScope::TimelineBeginEndScope(TimelineStream* stream, |
| const char* label) |
| : TimelineEventScope(stream, label) { |
| EmitBegin(); |
| } |
| |
| TimelineBeginEndScope::TimelineBeginEndScope(Thread* thread, |
| TimelineStream* stream, |
| const char* label) |
| : TimelineEventScope(thread, stream, label) { |
| EmitBegin(); |
| } |
| |
| TimelineBeginEndScope::~TimelineBeginEndScope() { |
| EmitEnd(); |
| } |
| |
| void TimelineBeginEndScope::EmitBegin() { |
| if (!ShouldEmitEvent()) { |
| return; |
| } |
| TimelineEvent* event = stream()->StartEvent(); |
| if (event == nullptr) { |
| // Stream is now disabled. |
| set_enabled(false); |
| return; |
| } |
| ASSERT(event != nullptr); |
| // Emit a begin event. |
| event->Begin(label(), id()); |
| event->Complete(); |
| } |
| |
| void TimelineBeginEndScope::EmitEnd() { |
| if (!ShouldEmitEvent()) { |
| return; |
| } |
| TimelineEvent* event = stream()->StartEvent(); |
| if (event == nullptr) { |
| // Stream is now disabled. |
| set_enabled(false); |
| return; |
| } |
| ASSERT(event != nullptr); |
| // Emit an end event. |
| event->End(label(), id()); |
| StealArguments(event); |
| event->Complete(); |
| } |
| |
| bool TimelineEventBlock::InUseLocked() const { |
| ASSERT(Timeline::recorder()->lock_.IsOwnedByCurrentThread()); |
| return in_use_; |
| } |
| |
| bool TimelineEventBlock::ContainsEventsThatCanBeSerializedLocked() const { |
| ASSERT(Timeline::recorder()->lock_.IsOwnedByCurrentThread()); |
| // Check that the block is not in use and not empty. |!block->in_use()| must |
| // be checked first because we are only holding |lock_|. Holding |lock_| |
| // makes it safe to call |in_use()| on any block, but only makes it safe to |
| // call |IsEmpty()| on blocks that are not in use. |
| return !InUseLocked() && !IsEmpty(); |
| } |
| |
| TimelineEventFilter::TimelineEventFilter(int64_t time_origin_micros, |
| int64_t time_extent_micros) |
| : time_origin_micros_(time_origin_micros), |
| time_extent_micros_(time_extent_micros) { |
| ASSERT(time_origin_micros_ >= -1); |
| ASSERT(time_extent_micros_ >= -1); |
| } |
| |
| TimelineEventFilter::~TimelineEventFilter() {} |
| |
| IsolateTimelineEventFilter::IsolateTimelineEventFilter( |
| Dart_Port isolate_id, |
| int64_t time_origin_micros, |
| int64_t time_extent_micros) |
| : TimelineEventFilter(time_origin_micros, time_extent_micros), |
| isolate_id_(isolate_id) {} |
| |
| TimelineEventRecorder::TimelineEventRecorder() |
| : time_low_micros_(0), |
| time_high_micros_(0), |
| track_uuid_to_track_metadata_lock_(), |
| track_uuid_to_track_metadata_( |
| &SimpleHashMap::SamePointerValue, |
| TimelineEventRecorder::kTrackUuidToTrackMetadataInitialCapacity), |
| async_track_uuid_to_track_metadata_lock_(), |
| async_track_uuid_to_track_metadata_( |
| &SimpleHashMap::SamePointerValue, |
| TimelineEventRecorder::kTrackUuidToTrackMetadataInitialCapacity) {} |
| |
| TimelineEventRecorder::~TimelineEventRecorder() { |
| // We do not need to lock the following section, because at this point |
| // |RecorderSynchronizationLock| must have been put in a state that prevents |
| // the metadata maps from being modified. |
| for (SimpleHashMap::Entry* entry = track_uuid_to_track_metadata_.Start(); |
| entry != nullptr; entry = track_uuid_to_track_metadata_.Next(entry)) { |
| TimelineTrackMetadata* value = |
| static_cast<TimelineTrackMetadata*>(entry->value); |
| delete value; |
| } |
| for (SimpleHashMap::Entry* entry = |
| async_track_uuid_to_track_metadata_.Start(); |
| entry != nullptr; |
| entry = async_track_uuid_to_track_metadata_.Next(entry)) { |
| AsyncTimelineTrackMetadata* value = |
| static_cast<AsyncTimelineTrackMetadata*>(entry->value); |
| delete value; |
| } |
| } |
| |
| #ifndef PRODUCT |
| void TimelineEventRecorder::PrintJSONMeta(const JSONArray& jsarr_events) { |
| MutexLocker ml(&track_uuid_to_track_metadata_lock_); |
| for (SimpleHashMap::Entry* entry = track_uuid_to_track_metadata_.Start(); |
| entry != nullptr; entry = track_uuid_to_track_metadata_.Next(entry)) { |
| TimelineTrackMetadata* value = |
| static_cast<TimelineTrackMetadata*>(entry->value); |
| value->PrintJSON(jsarr_events); |
| } |
| } |
| |
| #if defined(SUPPORT_PERFETTO) |
| void TimelineEventRecorder::PrintPerfettoMeta( |
| JSONBase64String* jsonBase64String) { |
| ASSERT(jsonBase64String != nullptr); |
| |
| perfetto_utils::PopulateClockSnapshotPacket(packet_.get()); |
| perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, &packet_); |
| packet_.Reset(); |
| perfetto_utils::PopulateProcessDescriptorPacket(packet_.get()); |
| perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, &packet_); |
| packet_.Reset(); |
| |
| { |
| MutexLocker ml(&async_track_uuid_to_track_metadata_lock_); |
| for (SimpleHashMap::Entry* entry = |
| async_track_uuid_to_track_metadata_.Start(); |
| entry != nullptr; |
| entry = async_track_uuid_to_track_metadata_.Next(entry)) { |
| AsyncTimelineTrackMetadata* value = |
| static_cast<AsyncTimelineTrackMetadata*>(entry->value); |
| value->PopulateTracePacket(packet_.get()); |
| perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, |
| &packet_); |
| packet_.Reset(); |
| } |
| } |
| |
| { |
| MutexLocker ml(&track_uuid_to_track_metadata_lock_); |
| for (SimpleHashMap::Entry* entry = track_uuid_to_track_metadata_.Start(); |
| entry != nullptr; entry = track_uuid_to_track_metadata_.Next(entry)) { |
| TimelineTrackMetadata* value = |
| static_cast<TimelineTrackMetadata*>(entry->value); |
| value->PopulateTracePacket(packet_.get()); |
| perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, |
| &packet_); |
| packet_.Reset(); |
| } |
| } |
| } |
| #endif // defined(SUPPORT_PERFETTO) |
| #endif // !defined(PRODUCT) |
| |
| TimelineEvent* TimelineEventRecorder::ThreadBlockStartEvent() { |
| // Grab the current thread. |
| OSThread* thread = OSThread::Current(); |
| ASSERT(thread != nullptr); |
| // Acquire the recorder lock in case we need to call |GetNewBlockLocked|. We |
| // acquire the lock here and not directly before calls to |GetNewBlockLocked| |
| // due to locking order restrictions. |
| Mutex& recorder_lock = lock_; |
| recorder_lock.Lock(); |
| Mutex* thread_block_lock = thread->timeline_block_lock(); |
| ASSERT(thread_block_lock != nullptr); |
| // We are accessing the thread's timeline block- so take the lock here. |
| // This lock will be held until the call to |CompleteEvent| is made. |
| thread_block_lock->Lock(); |
| #if defined(DEBUG) |
| Thread* T = Thread::Current(); |
| if (T != nullptr) { |
| T->IncrementNoSafepointScopeDepth(); |
| } |
| #endif // defined(DEBUG) |
| |
| TimelineEventBlock* thread_block = thread->TimelineBlockLocked(); |
| |
| if ((thread_block != nullptr) && thread_block->IsFull()) { |
| // Thread has a block and it is full: |
| // 1) Mark it as finished. |
| thread->SetTimelineBlockLocked(nullptr); |
| FinishBlock(thread_block); |
| // 2) Allocate a new block. |
| // We release |thread_block_lock| before calling |GetNewBlockLocked| to |
| // avoid TSAN warnings about lock order inversion. |
| thread_block_lock->Unlock(); |
| thread_block = GetNewBlockLocked(); |
| thread_block_lock->Lock(); |
| thread->SetTimelineBlockLocked(thread_block); |
| } else if (thread_block == nullptr) { |
| // Thread has no block. Attempt to allocate one. |
| // We release |thread_block_lock| before calling |GetNewBlockLocked| to |
| // avoid TSAN warnings about lock order inversion. |
| thread_block_lock->Unlock(); |
| thread_block = GetNewBlockLocked(); |
| thread_block_lock->Lock(); |
| thread->SetTimelineBlockLocked(thread_block); |
| } |
| recorder_lock.Unlock(); |
| if (thread_block != nullptr) { |
| // NOTE: We are exiting this function with the thread's block lock held. |
| ASSERT(!thread_block->IsFull()); |
| TimelineEvent* event = thread_block->StartEventLocked(); |
| return event; |
| } |
| // Drop lock here as no event is being handed out. |
| #if defined(DEBUG) |
| if (T != nullptr) { |
| T->DecrementNoSafepointScopeDepth(); |
| } |
| #endif // defined(DEBUG) |
| thread_block_lock->Unlock(); |
| return nullptr; |
| } |
| |
| void TimelineEventRecorder::ResetTimeTracking() { |
| time_high_micros_ = 0; |
| time_low_micros_ = kMaxInt64; |
| } |
| |
| void TimelineEventRecorder::ReportTime(int64_t micros) { |
| if (time_high_micros_ < micros) { |
| time_high_micros_ = micros; |
| } |
| if (time_low_micros_ > micros) { |
| time_low_micros_ = micros; |
| } |
| } |
| |
| int64_t TimelineEventRecorder::TimeOriginMicros() const { |
| if (time_high_micros_ == 0) { |
| return 0; |
| } |
| return time_low_micros_; |
| } |
| |
| int64_t TimelineEventRecorder::TimeExtentMicros() const { |
| if (time_high_micros_ == 0) { |
| return 0; |
| } |
| return time_high_micros_ - time_low_micros_; |
| } |
| |
| void TimelineEventRecorder::ThreadBlockCompleteEvent(TimelineEvent* event) { |
| if (event == nullptr) { |
| return; |
| } |
| #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) |
| // Async track metadata is only written in Perfetto traces, and Perfetto |
| // traces cannot be written when SUPPORT_PERFETTO is not defined, or when |
| // PRODUCT is defined. |
| if (event->event_type() == TimelineEvent::kAsyncBegin || |
| event->event_type() == TimelineEvent::kAsyncInstant) { |
| AddAsyncTrackMetadataBasedOnEvent(*event); |
| } |
| #endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) |
| // Grab the current thread. |
| OSThread* thread = OSThread::Current(); |
| ASSERT(thread != nullptr); |
| // Unlock the thread's block lock. |
| Mutex* thread_block_lock = thread->timeline_block_lock(); |
| ASSERT(thread_block_lock != nullptr); |
| #if defined(DEBUG) |
| Thread* T = Thread::Current(); |
| if (T != nullptr) { |
| T->DecrementNoSafepointScopeDepth(); |
| } |
| #endif // defined(DEBUG) |
| thread_block_lock->Unlock(); |
| } |
| |
| #ifndef PRODUCT |
| void TimelineEventRecorder::WriteTo(const char* directory) { |
| Dart_FileOpenCallback file_open = Dart::file_open_callback(); |
| Dart_FileWriteCallback file_write = Dart::file_write_callback(); |
| Dart_FileCloseCallback file_close = Dart::file_close_callback(); |
| if ((file_open == nullptr) || (file_write == nullptr) || |
| (file_close == nullptr)) { |
| OS::PrintErr("warning: Could not access file callbacks."); |
| return; |
| } |
| |
| // Acquire the recorder's lock to prevent the reclaimed blocks from being |
| // handed out again until the trace has been serialized. |
| MutexLocker ml(&lock_); |
| Timeline::ReclaimCachedBlocksFromThreads(); |
| |
| intptr_t pid = OS::ProcessId(); |
| char* filename = |
| OS::SCreate(nullptr, "%s/dart-timeline-%" Pd ".json", directory, pid); |
| void* file = (*file_open)(filename, true); |
| if (file == nullptr) { |
| OS::PrintErr("warning: Failed to write timeline file: %s\n", filename); |
| free(filename); |
| return; |
| } |
| free(filename); |
| |
| JSONStream js; |
| TimelineEventFilter filter; |
| PrintTraceEvent(&js, &filter); |
| // Steal output from JSONStream. |
| char* output = nullptr; |
| intptr_t output_length = 0; |
| js.Steal(&output, &output_length); |
| (*file_write)(output, output_length, file); |
| // Free the stolen output. |
| free(output); |
| (*file_close)(file); |
| |
| return; |
| } |
| #endif |
| |
| void TimelineEventRecorder::FinishBlock(TimelineEventBlock* block) { |
| if (block != nullptr) { |
| block->Finish(); |
| } |
| } |
| |
| void TimelineEventRecorder::AddTrackMetadataBasedOnThread( |
| const intptr_t process_id, |
| const intptr_t trace_id, |
| const char* thread_name) { |
| ASSERT(FLAG_timeline_recorder != nullptr); |
| if (strcmp("none", FLAG_timeline_recorder) == 0 || |
| strcmp("callback", FLAG_timeline_recorder) == 0 || |
| strcmp("systrace", FLAG_timeline_recorder) == 0 || |
| FLAG_systrace_timeline) { |
| // There is no way to retrieve track metadata when a no-op, callback, or |
| // systrace recorder is in use, so we don't need to update the map in these |
| // cases. |
| return; |
| } |
| MutexLocker ml(&track_uuid_to_track_metadata_lock_); |
| |
| void* key = reinterpret_cast<void*>(trace_id); |
| const intptr_t hash = Utils::WordHash(trace_id); |
| SimpleHashMap::Entry* entry = |
| track_uuid_to_track_metadata_.Lookup(key, hash, true); |
| if (entry->value == nullptr) { |
| entry->value = new TimelineTrackMetadata( |
| process_id, trace_id, |
| CStringUniquePtr( |
| Utils::StrDup(thread_name == nullptr ? "" : thread_name))); |
| } else { |
| TimelineTrackMetadata* value = |
| static_cast<TimelineTrackMetadata*>(entry->value); |
| ASSERT(process_id == value->pid()); |
| value->set_track_name(CStringUniquePtr( |
| Utils::StrDup(thread_name == nullptr ? "" : thread_name))); |
| } |
| } |
| |
| #if !defined(PRODUCT) |
| void TimelineEventRecorder::AddAsyncTrackMetadataBasedOnEvent( |
| const TimelineEvent& event) { |
| ASSERT(FLAG_timeline_recorder != nullptr); |
| if (strcmp("none", FLAG_timeline_recorder) == 0 || |
| strcmp("callback", FLAG_timeline_recorder) == 0 || |
| strcmp("systrace", FLAG_timeline_recorder) == 0 || |
| FLAG_systrace_timeline) { |
| // There is no way to retrieve track metadata when a no-op, callback, or |
| // systrace recorder is in use, so we don't need to update the map in |
| // these cases. |
| return; |
| } |
| MutexLocker ml(&async_track_uuid_to_track_metadata_lock_); |
| |
| void* key = reinterpret_cast<void*>(event.Id()); |
| const intptr_t hash = Utils::WordHash(event.Id()); |
| SimpleHashMap::Entry* entry = |
| async_track_uuid_to_track_metadata_.Lookup(key, hash, true); |
| if (entry->value == nullptr) { |
| entry->value = new AsyncTimelineTrackMetadata(OS::ProcessId(), event.Id()); |
| } |
| } |
| #endif // !defined(PRODUCT) |
| |
| TimelineEventFixedBufferRecorder::TimelineEventFixedBufferRecorder( |
| intptr_t capacity) |
| : memory_(nullptr), |
| blocks_(nullptr), |
| capacity_(capacity), |
| num_blocks_(0), |
| block_cursor_(0) { |
| // Capacity must be a multiple of TimelineEventBlock::kBlockSize |
| ASSERT((capacity % TimelineEventBlock::kBlockSize) == 0); |
| // Allocate blocks array. |
| num_blocks_ = capacity / TimelineEventBlock::kBlockSize; |
| |
| intptr_t size = Utils::RoundUp(num_blocks_ * sizeof(TimelineEventBlock), |
| VirtualMemory::PageSize()); |
| const bool executable = false; |
| const bool compressed = false; |
| memory_ = |
| VirtualMemory::Allocate(size, executable, compressed, "dart-timeline"); |
| if (memory_ == nullptr) { |
| OUT_OF_MEMORY(); |
| } |
| blocks_ = reinterpret_cast<TimelineEventBlock*>(memory_->address()); |
| } |
| |
| TimelineEventFixedBufferRecorder::~TimelineEventFixedBufferRecorder() { |
| // We do not need to acquire any locks, because at this point we must have |
| // reclaimed all the blocks, and |RecorderSynchronizationLock| must have been |
| // put in a state that prevents blocks from being given out. |
| delete memory_; |
| } |
| |
| intptr_t TimelineEventFixedBufferRecorder::Size() { |
| return memory_->size(); |
| } |
| |
| #ifndef PRODUCT |
| void TimelineEventFixedBufferRecorder::PrintEventsCommon( |
| const TimelineEventFilter& filter, |
| std::function<void(const TimelineEvent&)>&& print_impl) { |
| // Acquire the recorder's lock to prevent the reclaimed blocks from being |
| // handed out again until the trace has been serialized. |
| MutexLocker ml(&lock_); |
| Timeline::ReclaimCachedBlocksFromThreads(); |
| ResetTimeTracking(); |
| intptr_t block_offset = FindOldestBlockIndexLocked(); |
| if (block_offset == -1) { |
| // All blocks are in use or empty. |
| return; |
| } |
| for (intptr_t block_idx = 0; block_idx < num_blocks_; block_idx++) { |
| TimelineEventBlock* block = |
| &blocks_[(block_idx + block_offset) % num_blocks_]; |
| if (!block->ContainsEventsThatCanBeSerializedLocked()) { |
| continue; |
| } |
| for (intptr_t event_idx = 0; event_idx < block->length(); event_idx++) { |
| TimelineEvent* event = block->At(event_idx); |
| if (filter.IncludeEvent(event) && |
| event->Within(filter.time_origin_micros(), |
| filter.time_extent_micros())) { |
| ReportTime(event->LowTime()); |
| ReportTime(event->HighTime()); |
| print_impl(*event); |
| } |
| } |
| } |
| } |
| |
| void TimelineEventFixedBufferRecorder::PrintJSONEvents( |
| const JSONArray& events, |
| const TimelineEventFilter& filter) { |
| PrintEventsCommon(filter, [&events](const TimelineEvent& event) { |
| events.AddValue(&event); |
| }); |
| } |
| |
| #if defined(SUPPORT_PERFETTO) |
| // Populates the fields of |heap_buffered_packet| with the data in |event|, and |
| // then calls |print_callback| with the populated |heap_buffered_packet| as the |
| // only argument. This function resets |heap_buffered_packet| right before |
| // returning. |
| inline void PrintPerfettoEventCallbackBody( |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* |
| heap_buffered_packet, |
| const TimelineEvent& event, |
| const std::function< |
| void(protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>*)>&& |
| print_callback) { |
| ASSERT(heap_buffered_packet != nullptr); |
| if (!event.CanBeRepresentedByPerfettoTracePacket()) { |
| return; |
| } |
| if (event.IsDuration()) { |
| // Duration events must be converted to pairs of begin and end events to |
| // be serialized in Perfetto's format. |
| perfetto::protos::pbzero::TracePacket& packet = |
| *heap_buffered_packet->get(); |
| { |
| perfetto_utils::SetTrustedPacketSequenceId(&packet); |
| perfetto_utils::SetTimestampAndMonotonicClockId(&packet, |
| event.TimeOrigin()); |
| |
| perfetto::protos::pbzero::TrackEvent* track_event = |
| packet.set_track_event(); |
| track_event->add_categories(event.stream()->name()); |
| AddSyncEventFields(track_event, event); |
| AddBeginEventFields(track_event, event); |
| AddDebugAnnotations(track_event, event); |
| } |
| print_callback(heap_buffered_packet); |
| heap_buffered_packet->Reset(); |
| |
| { |
| perfetto_utils::SetTrustedPacketSequenceId(&packet); |
| perfetto_utils::SetTimestampAndMonotonicClockId(&packet, event.TimeEnd()); |
| |
| perfetto::protos::pbzero::TrackEvent* track_event = |
| packet.set_track_event(); |
| track_event->add_categories(event.stream()->name()); |
| AddSyncEventFields(track_event, event); |
| AddEndEventFields(track_event); |
| AddDebugAnnotations(track_event, event); |
| } |
| } else { |
| event.PopulateTracePacket(heap_buffered_packet->get()); |
| } |
| print_callback(heap_buffered_packet); |
| heap_buffered_packet->Reset(); |
| } |
| |
| void TimelineEventFixedBufferRecorder::PrintPerfettoEvents( |
| JSONBase64String* jsonBase64String, |
| const TimelineEventFilter& filter) { |
| PrintEventsCommon( |
| filter, [this, &jsonBase64String](const TimelineEvent& event) { |
| PrintPerfettoEventCallbackBody( |
| &packet(), event, |
| [&jsonBase64String]( |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* |
| packet) { |
| perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, |
| packet); |
| }); |
| }); |
| } |
| #endif // defined(SUPPORT_PERFETTO) |
| |
| void TimelineEventFixedBufferRecorder::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()); |
| } |
| |
| #define PRINT_PERFETTO_TIMELINE_BODY \ |
| 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()); |
| |
| #if defined(SUPPORT_PERFETTO) |
| void TimelineEventFixedBufferRecorder::PrintPerfettoTimeline( |
| JSONStream* js, |
| const TimelineEventFilter& filter) { |
| PRINT_PERFETTO_TIMELINE_BODY |
| } |
| #endif // defined(SUPPORT_PERFETTO) |
| |
| void TimelineEventFixedBufferRecorder::PrintTraceEvent( |
| JSONStream* js, |
| TimelineEventFilter* filter) { |
| JSONArray events(js); |
| PrintJSONMeta(events); |
| PrintJSONEvents(events, *filter); |
| } |
| #endif // !defined(PRODUCT) |
| |
| TimelineEventBlock* TimelineEventFixedBufferRecorder::GetHeadBlockLocked() { |
| ASSERT(lock_.IsOwnedByCurrentThread()); |
| return &blocks_[0]; |
| } |
| |
| void TimelineEventFixedBufferRecorder::ClearLocked() { |
| ASSERT(lock_.IsOwnedByCurrentThread()); |
| for (intptr_t i = 0; i < num_blocks_; i++) { |
| TimelineEventBlock* block = &blocks_[i]; |
| block->Reset(); |
| } |
| } |
| |
| intptr_t TimelineEventFixedBufferRecorder::FindOldestBlockIndexLocked() const { |
| ASSERT(lock_.IsOwnedByCurrentThread()); |
| int64_t earliest_time = kMaxInt64; |
| intptr_t earliest_index = -1; |
| for (intptr_t block_idx = 0; block_idx < num_blocks_; block_idx++) { |
| TimelineEventBlock* block = &blocks_[block_idx]; |
| if (!block->ContainsEventsThatCanBeSerializedLocked()) { |
| // Skip in use and empty blocks. |
| continue; |
| } |
| if (block->LowerTimeBound() < earliest_time) { |
| earliest_time = block->LowerTimeBound(); |
| earliest_index = block_idx; |
| } |
| } |
| return earliest_index; |
| } |
| |
| TimelineEvent* TimelineEventFixedBufferRecorder::StartEvent() { |
| return ThreadBlockStartEvent(); |
| } |
| |
| void TimelineEventFixedBufferRecorder::CompleteEvent(TimelineEvent* event) { |
| if (event == nullptr) { |
| return; |
| } |
| ThreadBlockCompleteEvent(event); |
| } |
| |
| TimelineEventBlock* TimelineEventRingRecorder::GetNewBlockLocked() { |
| ASSERT(lock_.IsOwnedByCurrentThread()); |
| if (block_cursor_ == num_blocks_) { |
| block_cursor_ = 0; |
| } |
| TimelineEventBlock* block = &blocks_[block_cursor_++]; |
| if (block->current_owner_ != nullptr) { |
| MutexLocker ml(block->current_owner_->timeline_block_lock()); |
| block->current_owner_->SetTimelineBlockLocked(nullptr); |
| block->Reset(); |
| block->Open(); |
| } else { |
| block->Reset(); |
| block->Open(); |
| } |
| return block; |
| } |
| |
| TimelineEventBlock* TimelineEventStartupRecorder::GetNewBlockLocked() { |
| ASSERT(lock_.IsOwnedByCurrentThread()); |
| if (block_cursor_ == num_blocks_) { |
| return nullptr; |
| } |
| TimelineEventBlock* block = &blocks_[block_cursor_++]; |
| block->Reset(); |
| block->Open(); |
| return block; |
| } |
| |
| TimelineEventCallbackRecorder::TimelineEventCallbackRecorder() {} |
| |
| TimelineEventCallbackRecorder::~TimelineEventCallbackRecorder() {} |
| |
| #ifndef PRODUCT |
| void TimelineEventCallbackRecorder::PrintJSON(JSONStream* js, |
| TimelineEventFilter* filter) { |
| UNREACHABLE(); |
| } |
| |
| #if defined(SUPPORT_PERFETTO) |
| void TimelineEventCallbackRecorder::PrintPerfettoTimeline( |
| JSONStream* js, |
| const TimelineEventFilter& filter) { |
| UNREACHABLE(); |
| } |
| #endif // defined(SUPPORT_PERFETTO) |
| |
| void TimelineEventCallbackRecorder::PrintTraceEvent( |
| JSONStream* js, |
| TimelineEventFilter* filter) { |
| JSONArray events(js); |
| } |
| #endif // !defined(PRODUCT) |
| |
| TimelineEvent* TimelineEventCallbackRecorder::StartEvent() { |
| TimelineEvent* event = new TimelineEvent(); |
| return event; |
| } |
| |
| void TimelineEventCallbackRecorder::CompleteEvent(TimelineEvent* event) { |
| OnEvent(event); |
| delete event; |
| } |
| |
| void TimelineEventEmbedderCallbackRecorder::OnEvent(TimelineEvent* event) { |
| Dart_TimelineRecorderCallback callback = Timeline::callback(); |
| if (callback == nullptr) { |
| return; |
| } |
| |
| Dart_TimelineRecorderEvent recorder_event; |
| recorder_event.version = DART_TIMELINE_RECORDER_CURRENT_VERSION; |
| switch (event->event_type()) { |
| case TimelineEvent::kBegin: |
| recorder_event.type = Dart_Timeline_Event_Begin; |
| break; |
| case TimelineEvent::kEnd: |
| recorder_event.type = Dart_Timeline_Event_End; |
| break; |
| case TimelineEvent::kInstant: |
| recorder_event.type = Dart_Timeline_Event_Instant; |
| break; |
| case TimelineEvent::kDuration: |
| recorder_event.type = Dart_Timeline_Event_Duration; |
| break; |
| case TimelineEvent::kAsyncBegin: |
| recorder_event.type = Dart_Timeline_Event_Async_Begin; |
| break; |
| case TimelineEvent::kAsyncEnd: |
| recorder_event.type = Dart_Timeline_Event_Async_End; |
| break; |
| case TimelineEvent::kAsyncInstant: |
| recorder_event.type = Dart_Timeline_Event_Async_Instant; |
| break; |
| case TimelineEvent::kCounter: |
| recorder_event.type = Dart_Timeline_Event_Counter; |
| break; |
| case TimelineEvent::kFlowBegin: |
| recorder_event.type = Dart_Timeline_Event_Flow_Begin; |
| break; |
| case TimelineEvent::kFlowStep: |
| recorder_event.type = Dart_Timeline_Event_Flow_Step; |
| break; |
| case TimelineEvent::kFlowEnd: |
| recorder_event.type = Dart_Timeline_Event_Flow_End; |
| break; |
| default: |
| // Type not expressible as Dart_Timeline_Event_Type: drop event. |
| return; |
| } |
| recorder_event.timestamp0 = event->timestamp0(); |
| recorder_event.timestamp1_or_id = event->timestamp1_or_id(); |
| recorder_event.isolate = event->isolate_id(); |
| recorder_event.isolate_group = event->isolate_group_id(); |
| recorder_event.isolate_data = event->isolate_data(); |
| recorder_event.isolate_group_data = event->isolate_group_data(); |
| recorder_event.label = event->label(); |
| recorder_event.stream = event->stream()->name(); |
| recorder_event.argument_count = event->GetNumArguments(); |
| recorder_event.arguments = |
| reinterpret_cast<Dart_TimelineRecorderEvent_Argument*>( |
| event->arguments()); |
| |
| NoActiveIsolateScope no_active_isolate_scope; |
| callback(&recorder_event); |
| } |
| |
| void TimelineEventNopRecorder::OnEvent(TimelineEvent* event) { |
| // Do nothing. |
| } |
| |
| TimelineEventPlatformRecorder::TimelineEventPlatformRecorder() {} |
| |
| TimelineEventPlatformRecorder::~TimelineEventPlatformRecorder() {} |
| |
| #ifndef PRODUCT |
| void TimelineEventPlatformRecorder::PrintJSON(JSONStream* js, |
| TimelineEventFilter* filter) { |
| UNREACHABLE(); |
| } |
| |
| #if defined(SUPPORT_PERFETTO) |
| void TimelineEventPlatformRecorder::PrintPerfettoTimeline( |
| JSONStream* js, |
| const TimelineEventFilter& filter) { |
| UNREACHABLE(); |
| } |
| #endif // defined(SUPPORT_PERFETTO) |
| |
| void TimelineEventPlatformRecorder::PrintTraceEvent( |
| JSONStream* js, |
| TimelineEventFilter* filter) { |
| JSONArray events(js); |
| } |
| #endif // !defined(PRODUCT) |
| |
| TimelineEvent* TimelineEventPlatformRecorder::StartEvent() { |
| TimelineEvent* event = new TimelineEvent(); |
| return event; |
| } |
| |
| void TimelineEventPlatformRecorder::CompleteEvent(TimelineEvent* event) { |
| OnEvent(event); |
| delete event; |
| } |
| |
| static void TimelineEventFileRecorderBaseStart(uword parameter) { |
| reinterpret_cast<TimelineEventFileRecorderBase*>(parameter)->Drain(); |
| } |
| |
| TimelineEventFileRecorderBase::TimelineEventFileRecorderBase(const char* path) |
| : TimelineEventPlatformRecorder(), |
| monitor_(), |
| head_(nullptr), |
| tail_(nullptr), |
| file_(nullptr), |
| shutting_down_(false), |
| drained_(false), |
| thread_id_(OSThread::kInvalidThreadJoinId) { |
| Dart_FileOpenCallback file_open = Dart::file_open_callback(); |
| Dart_FileWriteCallback file_write = Dart::file_write_callback(); |
| Dart_FileCloseCallback file_close = Dart::file_close_callback(); |
| if ((file_open == nullptr) || (file_write == nullptr) || |
| (file_close == nullptr)) { |
| OS::PrintErr("warning: Could not access file callbacks."); |
| return; |
| } |
| void* file = (*file_open)(path, true); |
| if (file == nullptr) { |
| OS::PrintErr("warning: Failed to open timeline file: %s\n", path); |
| return; |
| } |
| |
| file_ = file; |
| } |
| |
| TimelineEventFileRecorderBase::~TimelineEventFileRecorderBase() { |
| // WARNING: |ShutDown()| must be called in the derived class destructor. This |
| // work cannot be performed in this destructor, because then |DrainImpl()| |
| // might run between when the derived class destructor completes, and when |
| // |shutting_down_| is set to true, causing possible use-after-free errors. |
| ASSERT(shutting_down_); |
| |
| if (file_ == nullptr) return; |
| |
| ASSERT(thread_id_ != OSThread::kInvalidThreadJoinId); |
| OSThread::Join(thread_id_); |
| thread_id_ = OSThread::kInvalidThreadJoinId; |
| |
| ASSERT(head_ == nullptr); |
| ASSERT(tail_ == nullptr); |
| |
| 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 (head_ == nullptr) { |
| if (shutting_down_) { |
| break; |
| } |
| ml.Wait(); |
| continue; // Recheck empty. |
| } |
| TimelineEvent* event = head_; |
| TimelineEvent* next = event->next(); |
| head_ = next; |
| if (next == nullptr) { |
| tail_ = nullptr; |
| } |
| ml.Exit(); |
| { |
| DrainImpl(*event); |
| delete event; |
| } |
| ml.Enter(); |
| } |
| drained_ = true; |
| ml.Notify(); |
| } |
| |
| void TimelineEventFileRecorderBase::Write(const char* buffer, |
| intptr_t len) const { |
| Dart_FileWriteCallback file_write = Dart::file_write_callback(); |
| (*file_write)(buffer, len, file_); |
| } |
| |
| void TimelineEventFileRecorderBase::CompleteEvent(TimelineEvent* event) { |
| if (event == nullptr) { |
| return; |
| } |
| if (file_ == nullptr) { |
| delete event; |
| return; |
| } |
| |
| MonitorLocker ml(&monitor_); |
| ASSERT(!shutting_down_); |
| event->set_next(nullptr); |
| if (tail_ == nullptr) { |
| head_ = tail_ = event; |
| } else { |
| tail_->set_next(event); |
| tail_ = event; |
| } |
| 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(); |
| } |
| } |
| |
| TimelineEventFileRecorder::TimelineEventFileRecorder(const char* path) |
| : TimelineEventFileRecorderBase(path), first_(true) { |
| // Chrome trace format has two forms: |
| // Object form: { "traceEvents": [ event, event, event ] } |
| // Array form: [ event, event, event ] |
| // For this recorder, we use the array form because Catapult will handle a |
| // missing ending bracket in this form in case we don't cleanly end the |
| // trace. |
| Write("[\n"); |
| StartUp("TimelineEventFileRecorder"); |
| } |
| |
| TimelineEventFileRecorder::~TimelineEventFileRecorder() { |
| ShutDown(); |
| Write("]\n"); |
| } |
| |
| void TimelineEventFileRecorder::AddTrackMetadataBasedOnThread( |
| const intptr_t process_id, |
| const intptr_t trace_id, |
| const char* thread_name) { |
| TimelineEvent* event = new TimelineEvent(); |
| event->Metadata("thread_name"); |
| event->SetNumArguments(1); |
| event->CopyArgument(0, "name", thread_name); |
| CompleteEvent(event); |
| } |
| |
| void TimelineEventFileRecorder::DrainImpl(const TimelineEvent& event) { |
| JSONWriter writer; |
| if (first_) { |
| first_ = false; |
| } else { |
| writer.buffer()->AddChar(','); |
| } |
| event.PrintJSON(&writer); |
| char* output = nullptr; |
| intptr_t output_length = 0; |
| writer.Steal(&output, &output_length); |
| Write(output, output_length); |
| free(output); |
| } |
| |
| #if defined(SUPPORT_PERFETTO) && !defined(PRODUCT) |
| TimelineEventPerfettoFileRecorder::TimelineEventPerfettoFileRecorder( |
| const char* path) |
| : TimelineEventFileRecorderBase(path) { |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>& packet = |
| this->packet(); |
| |
| perfetto_utils::PopulateClockSnapshotPacket(packet.get()); |
| WritePacket(&packet); |
| packet.Reset(); |
| |
| perfetto_utils::PopulateProcessDescriptorPacket(packet.get()); |
| WritePacket(&packet); |
| packet.Reset(); |
| |
| StartUp("TimelineEventPerfettoFileRecorder"); |
| } |
| |
| TimelineEventPerfettoFileRecorder::~TimelineEventPerfettoFileRecorder() { |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>& packet = |
| this->packet(); |
| ShutDown(); |
| // We do not need to lock the following section, because at this point |
| // |RecorderSynchronizationLock| must have been put in a state that prevents |
| // the metadata maps from being modified. |
| for (SimpleHashMap::Entry* entry = track_uuid_to_track_metadata().Start(); |
| entry != nullptr; entry = track_uuid_to_track_metadata().Next(entry)) { |
| TimelineTrackMetadata* value = |
| static_cast<TimelineTrackMetadata*>(entry->value); |
| value->PopulateTracePacket(packet.get()); |
| WritePacket(&packet); |
| packet.Reset(); |
| } |
| for (SimpleHashMap::Entry* entry = |
| async_track_uuid_to_track_metadata().Start(); |
| entry != nullptr; |
| entry = async_track_uuid_to_track_metadata().Next(entry)) { |
| AsyncTimelineTrackMetadata* value = |
| static_cast<AsyncTimelineTrackMetadata*>(entry->value); |
| value->PopulateTracePacket(packet.get()); |
| WritePacket(&packet); |
| packet.Reset(); |
| } |
| } |
| |
| void TimelineEventPerfettoFileRecorder::WritePacket( |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* packet) |
| const { |
| 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<char*>(slice.start()), |
| slice.size() - slice.unused_bytes()); |
| } |
| } |
| |
| void TimelineEventPerfettoFileRecorder::DrainImpl(const TimelineEvent& event) { |
| PrintPerfettoEventCallbackBody( |
| &packet(), event, |
| [this](protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* |
| packet) { WritePacket(packet); }); |
| |
| if (event.event_type() == TimelineEvent::kAsyncBegin || |
| event.event_type() == TimelineEvent::kAsyncInstant) { |
| AddAsyncTrackMetadataBasedOnEvent(event); |
| } |
| } |
| #endif // defined(SUPPORT_PERFETTO) && !defined(PRODUCT) |
| |
| TimelineEventEndlessRecorder::TimelineEventEndlessRecorder() |
| : head_(nullptr), tail_(nullptr), block_index_(0) {} |
| |
| TimelineEventEndlessRecorder::~TimelineEventEndlessRecorder() { |
| ASSERT(head_ == nullptr); |
| } |
| |
| #ifndef PRODUCT |
| void TimelineEventEndlessRecorder::PrintEventsCommon( |
| const TimelineEventFilter& filter, |
| std::function<void(const TimelineEvent&)>&& print_impl) { |
| // Acquire the recorder's lock to prevent the reclaimed blocks from being |
| // handed out again until the trace has been serialized. |
| MutexLocker ml(&lock_); |
| Timeline::ReclaimCachedBlocksFromThreads(); |
| ResetTimeTracking(); |
| for (TimelineEventBlock* current = head_; current != nullptr; |
| current = current->next()) { |
| if (!current->ContainsEventsThatCanBeSerializedLocked()) { |
| continue; |
| } |
| intptr_t length = current->length(); |
| for (intptr_t i = 0; i < length; i++) { |
| TimelineEvent* event = current->At(i); |
| if (filter.IncludeEvent(event) && |
| event->Within(filter.time_origin_micros(), |
| filter.time_extent_micros())) { |
| ReportTime(event->LowTime()); |
| ReportTime(event->HighTime()); |
| print_impl(*event); |
| } |
| } |
| } |
| } |
| |
| void TimelineEventEndlessRecorder::PrintJSONEvents( |
| const JSONArray& events, |
| const TimelineEventFilter& filter) { |
| PrintEventsCommon(filter, [&events](const TimelineEvent& event) { |
| events.AddValue(&event); |
| }); |
| } |
| |
| #if defined(SUPPORT_PERFETTO) |
| void TimelineEventEndlessRecorder::PrintPerfettoEvents( |
| JSONBase64String* jsonBase64String, |
| const TimelineEventFilter& filter) { |
| PrintEventsCommon( |
| filter, [this, &jsonBase64String](const TimelineEvent& event) { |
| PrintPerfettoEventCallbackBody( |
| &packet(), event, |
| [&jsonBase64String]( |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>* |
| packet) { |
| perfetto_utils::AppendPacketToJSONBase64String(jsonBase64String, |
| packet); |
| }); |
| }); |
| } |
| #endif // defined(SUPPORT_PERFETTO) |
| |
| void TimelineEventEndlessRecorder::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 TimelineEventEndlessRecorder::PrintPerfettoTimeline( |
| JSONStream* js, |
| const TimelineEventFilter& filter) { |
| PRINT_PERFETTO_TIMELINE_BODY |
| } |
| #endif // defined(SUPPORT_PERFETTO) |
| |
| void TimelineEventEndlessRecorder::PrintTraceEvent( |
| JSONStream* js, |
| TimelineEventFilter* filter) { |
| JSONArray events(js); |
| PrintJSONMeta(events); |
| PrintJSONEvents(events, *filter); |
| } |
| #endif // !defined(PRODUCT) |
| |
| TimelineEventBlock* TimelineEventEndlessRecorder::GetHeadBlockLocked() { |
| ASSERT(lock_.IsOwnedByCurrentThread()); |
| return head_; |
| } |
| |
| TimelineEvent* TimelineEventEndlessRecorder::StartEvent() { |
| return ThreadBlockStartEvent(); |
| } |
| |
| void TimelineEventEndlessRecorder::CompleteEvent(TimelineEvent* event) { |
| if (event == nullptr) { |
| return; |
| } |
| ThreadBlockCompleteEvent(event); |
| } |
| |
| TimelineEventBlock* TimelineEventEndlessRecorder::GetNewBlockLocked() { |
| ASSERT(lock_.IsOwnedByCurrentThread()); |
| TimelineEventBlock* block = new TimelineEventBlock(block_index_++); |
| block->Open(); |
| if (head_ == nullptr) { |
| head_ = tail_ = block; |
| } else { |
| tail_->set_next(block); |
| tail_ = block; |
| } |
| if (FLAG_trace_timeline) { |
| OS::PrintErr("Created new block %p\n", block); |
| } |
| return block; |
| } |
| |
| void TimelineEventEndlessRecorder::ClearLocked() { |
| ASSERT(lock_.IsOwnedByCurrentThread()); |
| TimelineEventBlock* current = head_; |
| while (current != nullptr) { |
| TimelineEventBlock* next = current->next(); |
| delete current; |
| current = next; |
| } |
| head_ = nullptr; |
| tail_ = nullptr; |
| block_index_ = 0; |
| } |
| |
| TimelineEventBlock::TimelineEventBlock(intptr_t block_index) |
| : next_(nullptr), |
| length_(0), |
| block_index_(block_index), |
| current_owner_(nullptr), |
| in_use_(false) {} |
| |
| TimelineEventBlock::~TimelineEventBlock() { |
| Reset(); |
| } |
| |
| #ifndef PRODUCT |
| void TimelineEventBlock::PrintJSON(JSONStream* js) const { |
| ASSERT(!InUseLocked()); |
| JSONArray events(js); |
| for (intptr_t i = 0; i < length(); i++) { |
| const TimelineEvent* event = At(i); |
| if (event->IsValid()) { |
| events.AddValue(event); |
| } |
| } |
| } |
| #endif |
| |
| TimelineEvent* TimelineEventBlock::StartEventLocked() { |
| OSThread* os_thread = OSThread::Current(); |
| ASSERT(os_thread != nullptr); |
| ASSERT(os_thread == current_owner_); |
| ASSERT(os_thread->timeline_block_lock()->IsOwnedByCurrentThread()); |
| ASSERT(!IsFull()); |
| if (FLAG_trace_timeline) { |
| intptr_t tid = OSThread::ThreadIdToIntPtr(os_thread->id()); |
| OS::PrintErr("StartEvent in block %p for thread %" Pd "\n", this, tid); |
| } |
| return &events_[length_++]; |
| } |
| |
| int64_t TimelineEventBlock::LowerTimeBound() const { |
| if (length_ == 0) { |
| return kMaxInt64; |
| } |
| ASSERT(length_ > 0); |
| return events_[0].TimeOrigin(); |
| } |
| |
| void TimelineEventBlock::Reset() { |
| for (intptr_t i = 0; i < kBlockSize; i++) { |
| // Clear any extra data. |
| events_[i].Reset(); |
| } |
| length_ = 0; |
| current_owner_ = nullptr; |
| in_use_ = false; |
| } |
| |
| void TimelineEventBlock::Open() { |
| OSThread* os_thread = OSThread::Current(); |
| ASSERT(os_thread != nullptr); |
| current_owner_ = os_thread; |
| in_use_ = true; |
| } |
| |
| void TimelineEventBlock::Finish() { |
| if (FLAG_trace_timeline) { |
| OS::PrintErr("Finish block %p\n", this); |
| } |
| current_owner_ = nullptr; |
| in_use_ = false; |
| #ifndef PRODUCT |
| if (Service::timeline_stream.enabled()) { |
| ServiceEvent service_event(ServiceEvent::kTimelineEvents); |
| service_event.set_timeline_event_block(this); |
| Service::HandleEvent(&service_event, /* enter_safepoint */ false); |
| } |
| #endif |
| } |
| |
| void DartTimelineEventHelpers::ReportTaskEvent( |
| TimelineEvent* event, |
| int64_t id, |
| intptr_t flow_id_count, |
| std::unique_ptr<const int64_t[]>& flow_ids, |
| intptr_t type, |
| char* name, |
| char* args) { |
| const int64_t start = OS::GetCurrentMonotonicMicrosForTimeline(); |
| switch (static_cast<TimelineEvent::EventType>(type)) { |
| case TimelineEvent::kAsyncInstant: |
| event->AsyncInstant(name, id, start); |
| break; |
| case TimelineEvent::kAsyncBegin: |
| event->AsyncBegin(name, id, start); |
| break; |
| case TimelineEvent::kAsyncEnd: |
| event->AsyncEnd(name, id, start); |
| break; |
| case TimelineEvent::kBegin: |
| event->Begin(name, id, start); |
| break; |
| case TimelineEvent::kEnd: |
| event->End(name, id, start); |
| break; |
| case TimelineEvent::kFlowBegin: |
| event->FlowBegin(name, id, start); |
| break; |
| case TimelineEvent::kFlowStep: |
| event->FlowStep(name, id, start); |
| break; |
| case TimelineEvent::kFlowEnd: |
| event->FlowEnd(name, id, start); |
| break; |
| case TimelineEvent::kInstant: |
| event->Instant(name, start); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| if (flow_id_count > 0) { |
| ASSERT(type == TimelineEvent::kBegin || type == TimelineEvent::kInstant || |
| type == TimelineEvent::kAsyncBegin || |
| type == TimelineEvent::kAsyncInstant); |
| |
| event->SetFlowIds(flow_id_count, flow_ids); |
| } |
| event->set_owns_label(true); |
| event->CompleteWithPreSerializedArgs(args); |
| } |
| |
| } // namespace dart |
| |
| #endif // defined(SUPPORT_TIMELINE) |