8649806076
Use relaxed atomics for sample fields. Fix lock grabbing ordering. BUG=https://github.com/dart-lang/sdk/issues/62873 TEST=ci Change-Id: I9c4ae0c78d5b81a72dc8a9e4802f08fd57beb27a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/493161 Reviewed-by: Ryan Macnak <rmacnak@google.com> Commit-Queue: Alexander Aprelev <aam@google.com>
2878 lines
92 KiB
C++
2878 lines
92 KiB
C++
// 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;
|
|
const bool compressed = false;
|
|
memory_ =
|
|
VirtualMemory::Allocate(size, executable, compressed, "dart-timeline");
|
|
if (memory_ == nullptr) {
|
|
OUT_OF_MEMORY();
|
|
}
|
|
blocks_ = reinterpret_cast<TimelineEventBlock*>(memory_->address());
|
|
}
|
|
|
|
TimelineEventFixedBufferRecorder::~TimelineEventFixedBufferRecorder() {
|
|
// We do not need to acquire any locks, because at this point we must have
|
|
// reclaimed all the blocks, and |RecorderSynchronizationLock| must have been
|
|
// put in a state that prevents blocks from being given out.
|
|
delete memory_;
|
|
}
|
|
|
|
intptr_t TimelineEventFixedBufferRecorder::Size() {
|
|
return memory_->size();
|
|
}
|
|
|
|
#ifndef PRODUCT
|
|
void TimelineEventFixedBufferRecorder::ForEachNonEmptyBlock(
|
|
std::function<void(const TimelineEventBlock&)>&& handle_block) {
|
|
// Acquire the recorder's lock to prevent the reclaimed blocks from being
|
|
// handed out again until the trace has been serialized.
|
|
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 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);
|
|
HandleScope handle_scope(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)
|