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