blob: 6b96b167e9ee9b70e8745717333d27d785e414b7 [file] [log] [blame]
// 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 <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/thread.h"
#include "vm/timeline.h"
namespace dart {
DEFINE_FLAG(bool, complete_timeline, false, "Record the complete timeline");
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.");
// Implementation notes:
//
// Writing events:
// |TimelineEvent|s are written into |TimelineEventBlock|s. Each |Thread| caches
// a |TimelineEventBlock| in TLS 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. In order to report a complete timeline
// the cached |TimelineEventBlock|s need to be reclaimed.
//
// Reclaiming open |TimelineEventBlock|s for an isolate:
//
// Cached |TimelineEventBlock|s can be in two places:
// 1) In a |Thread| (Thread currently in an |Isolate|)
// 2) In a |Thread::State| (Thread not currently in an |Isolate|).
//
// As a |Thread| enters and exits an |Isolate|, a |TimelineEventBlock|
// will move between (1) and (2).
//
// The first case occurs for |Thread|s that are currently running inside an
// isolate. The second case occurs for |Thread|s that are not currently
// running inside an isolate.
//
// To reclaim the first case, we take the |Thread|'s |timeline_block_lock_|
// and reclaim the cached block.
//
// To reclaim the second case, we can take the |ThreadRegistry| lock and
// reclaim these blocks.
//
// |Timeline::ReclaimIsolateBlocks| and |Timeline::ReclaimAllBlocks| are
// the two utility methods used to reclaim blocks before reporting.
//
// 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.
// - |ThreadRegistry::monitor_| This lock protects the cached block for
// unscheduled threads of an isolate.
// - |Isolate::isolates_list_monitor_| This lock protects the list of
// isolates in the system.
//
// Locks must always be taken in the following order:
// |Isolate::isolates_list_monitor_|
// |ThreadRegistry::monitor_|
// |Thread::timeline_block_lock_|
// |TimelineEventRecorder::lock_|
//
void Timeline::InitOnce() {
ASSERT(recorder_ == NULL);
// Default to ring recorder being enabled.
const bool use_ring_recorder = true;
// Some flags require that we use the endless recorder.
const bool use_endless_recorder =
(FLAG_timeline_dir != NULL) || FLAG_timing;
if (use_endless_recorder) {
recorder_ = new TimelineEventEndlessRecorder();
} else if (use_ring_recorder) {
recorder_ = new TimelineEventRingRecorder();
}
vm_stream_ = new TimelineStream();
vm_stream_->Init("VM", EnableStreamByDefault("VM"), NULL);
// Global overrides.
#define ISOLATE_TIMELINE_STREAM_FLAG_DEFAULT(name, not_used) \
stream_##name##_enabled_ = false;
ISOLATE_TIMELINE_STREAM_LIST(ISOLATE_TIMELINE_STREAM_FLAG_DEFAULT)
#undef ISOLATE_TIMELINE_STREAM_FLAG_DEFAULT
}
void Timeline::Shutdown() {
ASSERT(recorder_ != NULL);
if (FLAG_timeline_dir != NULL) {
recorder_->WriteTo(FLAG_timeline_dir);
}
delete recorder_;
recorder_ = NULL;
delete vm_stream_;
vm_stream_ = NULL;
}
TimelineEventRecorder* Timeline::recorder() {
return recorder_;
}
bool Timeline::EnableStreamByDefault(const char* stream_name) {
// TODO(johnmccutchan): Allow for command line control over streams.
return (FLAG_timeline_dir != NULL) || FLAG_timing;
}
TimelineStream* Timeline::GetVMStream() {
ASSERT(vm_stream_ != NULL);
return vm_stream_;
}
void Timeline::ReclaimIsolateBlocks() {
ReclaimBlocksForIsolate(Isolate::Current());
}
class ReclaimBlocksIsolateVisitor : public IsolateVisitor {
public:
ReclaimBlocksIsolateVisitor() {}
virtual void VisitIsolate(Isolate* isolate) {
Timeline::ReclaimBlocksForIsolate(isolate);
}
private:
};
void Timeline::ReclaimAllBlocks() {
if (recorder() == NULL) {
return;
}
// Reclaim all blocks cached for all isolates.
ReclaimBlocksIsolateVisitor visitor;
Isolate::VisitIsolates(&visitor);
// Reclaim the global VM block.
recorder()->ReclaimGlobalBlock();
}
void Timeline::ReclaimBlocksForIsolate(Isolate* isolate) {
if (recorder() == NULL) {
return;
}
ASSERT(isolate != NULL);
isolate->ReclaimTimelineBlocks();
}
TimelineEventRecorder* Timeline::recorder_ = NULL;
TimelineStream* Timeline::vm_stream_ = NULL;
#define ISOLATE_TIMELINE_STREAM_DEFINE_FLAG(name, enabled_by_default) \
bool Timeline::stream_##name##_enabled_ = false;
ISOLATE_TIMELINE_STREAM_LIST(ISOLATE_TIMELINE_STREAM_DEFINE_FLAG)
#undef ISOLATE_TIMELINE_STREAM_DEFINE_FLAG
TimelineEvent::TimelineEvent()
: timestamp0_(0),
timestamp1_(0),
arguments_(NULL),
arguments_length_(0),
state_(0),
label_(NULL),
category_(""),
thread_(OSThread::kInvalidThreadId) {
}
TimelineEvent::~TimelineEvent() {
Reset();
}
void TimelineEvent::Reset() {
set_event_type(kNone);
thread_ = OSThread::kInvalidThreadId;
isolate_ = NULL;
category_ = "";
label_ = NULL;
FreeArguments();
}
void TimelineEvent::AsyncBegin(const char* label, int64_t async_id) {
Init(kAsyncBegin, label);
timestamp0_ = OS::GetCurrentTraceMicros();
// Overload timestamp1_ with the async_id.
timestamp1_ = async_id;
}
void TimelineEvent::AsyncInstant(const char* label,
int64_t async_id) {
Init(kAsyncInstant, label);
timestamp0_ = OS::GetCurrentTraceMicros();
// Overload timestamp1_ with the async_id.
timestamp1_ = async_id;
}
void TimelineEvent::AsyncEnd(const char* label,
int64_t async_id) {
Init(kAsyncEnd, label);
timestamp0_ = OS::GetCurrentTraceMicros();
// Overload timestamp1_ with the async_id.
timestamp1_ = async_id;
}
void TimelineEvent::DurationBegin(const char* label) {
Init(kDuration, label);
timestamp0_ = OS::GetCurrentTraceMicros();
}
void TimelineEvent::DurationEnd() {
timestamp1_ = OS::GetCurrentTraceMicros();
}
void TimelineEvent::Instant(const char* label) {
Init(kInstant, label);
timestamp0_ = OS::GetCurrentTraceMicros();
}
void TimelineEvent::Duration(const char* label,
int64_t start_micros,
int64_t end_micros) {
Init(kDuration, label);
timestamp0_ = start_micros;
timestamp1_ = end_micros;
}
void TimelineEvent::Begin(const char* label,
int64_t micros) {
Init(kBegin, label);
timestamp0_ = micros;
}
void TimelineEvent::End(const char* label,
int64_t micros) {
Init(kEnd, label);
timestamp0_ = micros;
}
void TimelineEvent::SetNumArguments(intptr_t length) {
// Cannot call this twice.
ASSERT(arguments_ == NULL);
ASSERT(arguments_length_ == 0);
if (length == 0) {
return;
}
arguments_length_ = length;
arguments_ = reinterpret_cast<TimelineEventArgument*>(
calloc(sizeof(TimelineEventArgument), length));
}
void TimelineEvent::SetArgument(intptr_t i, const char* name, char* argument) {
ASSERT(i >= 0);
ASSERT(i < arguments_length_);
arguments_[i].name = name;
arguments_[i].value = argument;
}
void TimelineEvent::FormatArgument(intptr_t i, const char* name,
const char* fmt, ...) {
ASSERT(i >= 0);
ASSERT(i < arguments_length_);
va_list args;
va_start(args, fmt);
intptr_t len = OS::VSNPrint(NULL, 0, fmt, args);
va_end(args);
char* buffer = reinterpret_cast<char*>(malloc(len + 1));
va_list args2;
va_start(args2, fmt);
OS::VSNPrint(buffer, (len + 1), fmt, args2);
va_end(args2);
SetArgument(i, name, buffer);
}
void TimelineEvent::CopyArgument(intptr_t i,
const char* name,
const char* argument) {
SetArgument(i, name, strdup(argument));
}
void TimelineEvent::StealArguments(intptr_t arguments_length,
TimelineEventArgument* arguments) {
arguments_length_ = arguments_length;
arguments_ = arguments;
}
void TimelineEvent::Complete() {
TimelineEventRecorder* recorder = Timeline::recorder();
if (recorder != NULL) {
recorder->CompleteEvent(this);
}
}
void TimelineEvent::FreeArguments() {
if (arguments_ == NULL) {
return;
}
for (intptr_t i = 0; i < arguments_length_; i++) {
free(arguments_[i].value);
}
free(arguments_);
arguments_ = NULL;
arguments_length_ = 0;
}
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);
set_event_type(event_type);
timestamp0_ = 0;
timestamp1_ = 0;
thread_ = OSThread::GetCurrentThreadTraceId();
isolate_ = Isolate::Current();
label_ = label;
FreeArguments();
}
void TimelineEvent::PrintJSON(JSONStream* stream) const {
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());
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());
}
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;
default:
UNIMPLEMENTED();
}
{
JSONObject args(&obj, "args");
for (intptr_t i = 0; i < arguments_length_; i++) {
const TimelineEventArgument& arg = arguments_[i];
args.AddProperty(arg.name, arg.value);
}
}
}
int64_t TimelineEvent::TimeOrigin() const {
return timestamp0_;
}
int64_t TimelineEvent::AsyncId() const {
return timestamp1_;
}
int64_t TimelineEvent::TimeDuration() const {
if (timestamp1_ == 0) {
// This duration is still open, use current time as end.
return OS::GetCurrentTraceMicros() - timestamp0_;
}
return timestamp1_ - timestamp0_;
}
TimelineStream::TimelineStream()
: name_(NULL),
enabled_(false),
globally_enabled_(NULL) {
}
void TimelineStream::Init(const char* name,
bool enabled,
const bool* globally_enabled) {
name_ = name;
enabled_ = enabled;
globally_enabled_ = globally_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;
}
TimelineDurationScope::TimelineDurationScope(TimelineStream* stream,
const char* label)
: StackResource(reinterpret_cast<Thread*>(NULL)),
timestamp_(0),
stream_(stream),
label_(label),
arguments_(NULL),
arguments_length_(0),
enabled_(false) {
Init();
}
TimelineDurationScope::TimelineDurationScope(Thread* thread,
TimelineStream* stream,
const char* label)
: StackResource(thread),
timestamp_(0),
stream_(stream),
label_(label),
arguments_(NULL),
arguments_length_(0),
enabled_(false) {
ASSERT(thread != NULL);
Init();
}
TimelineDurationScope::~TimelineDurationScope() {
if (!enabled_) {
FreeArguments();
return;
}
TimelineEvent* event = stream_->StartEvent();
ASSERT(event != NULL);
event->Duration(label_, timestamp_, OS::GetCurrentTraceMicros());
event->StealArguments(arguments_length_, arguments_);
event->Complete();
arguments_length_ = 0;
arguments_ = NULL;
}
void TimelineDurationScope::Init() {
ASSERT(enabled_ == false);
ASSERT(label_ != NULL);
ASSERT(stream_ != NULL);
if (!stream_->Enabled()) {
// Stream is not enabled, do nothing.
return;
}
timestamp_ = OS::GetCurrentTraceMicros();
enabled_ = true;
}
void TimelineDurationScope::SetNumArguments(intptr_t length) {
if (!enabled()) {
return;
}
ASSERT(arguments_ == NULL);
ASSERT(arguments_length_ == 0);
arguments_length_ = length;
if (arguments_length_ == 0) {
return;
}
arguments_ = reinterpret_cast<TimelineEventArgument*>(
calloc(sizeof(TimelineEventArgument), length));
}
// |name| must be a compile time constant. Takes ownership of |argumentp|.
void TimelineDurationScope::SetArgument(intptr_t i,
const char* name,
char* argument) {
if (!enabled()) {
return;
}
ASSERT(i >= 0);
ASSERT(i < arguments_length_);
arguments_[i].name = name;
arguments_[i].value = argument;
}
// |name| must be a compile time constant. Copies |argument|.
void TimelineDurationScope::CopyArgument(intptr_t i,
const char* name,
const char* argument) {
if (!enabled()) {
return;
}
SetArgument(i, name, strdup(argument));
}
void TimelineDurationScope::FormatArgument(intptr_t i,
const char* name,
const char* fmt, ...) {
if (!enabled()) {
return;
}
va_list args;
va_start(args, fmt);
intptr_t len = OS::VSNPrint(NULL, 0, fmt, args);
va_end(args);
char* buffer = reinterpret_cast<char*>(malloc(len + 1));
va_list args2;
va_start(args2, fmt);
OS::VSNPrint(buffer, (len + 1), fmt, args2);
va_end(args2);
SetArgument(i, name, buffer);
}
void TimelineDurationScope::FreeArguments() {
if (arguments_ == NULL) {
return;
}
for (intptr_t i = 0; i < arguments_length_; i++) {
free(arguments_[i].value);
}
free(arguments_);
arguments_ = NULL;
arguments_length_ = 0;
}
TimelineEventFilter::TimelineEventFilter() {
}
TimelineEventFilter::~TimelineEventFilter() {
}
IsolateTimelineEventFilter::IsolateTimelineEventFilter(Isolate* isolate)
: isolate_(isolate) {
}
DartTimelineEvent::DartTimelineEvent()
: isolate_(NULL),
event_as_json_(NULL) {
}
DartTimelineEvent::~DartTimelineEvent() {
Clear();
}
void DartTimelineEvent::Clear() {
if (isolate_ != NULL) {
isolate_ = NULL;
}
if (event_as_json_ != NULL) {
free(event_as_json_);
event_as_json_ = NULL;
}
}
void DartTimelineEvent::Init(Isolate* isolate, const char* event) {
ASSERT(isolate_ == NULL);
ASSERT(event != NULL);
isolate_ = isolate;
event_as_json_ = strdup(event);
}
TimelineEventRecorder::TimelineEventRecorder()
: global_block_(NULL),
async_id_(0) {
}
void TimelineEventRecorder::PrintJSONMeta(JSONArray* events) const {
}
TimelineEvent* TimelineEventRecorder::ThreadBlockStartEvent() {
// Grab the current thread.
Thread* thread = Thread::Current();
ASSERT(thread != NULL);
ASSERT(thread->isolate() != 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();
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->isolate());
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->isolate());
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();
if (event != NULL) {
event->set_global_block(false);
}
return event;
}
// Drop lock here as no event is being handed out.
thread_block_lock->Unlock();
return NULL;
}
TimelineEvent* TimelineEventRecorder::GlobalBlockStartEvent() {
// Take recorder lock. This lock will be held until the call to
// |CompleteEvent| is made.
lock_.Lock();
if (FLAG_trace_timeline) {
OS::Print("GlobalBlockStartEvent in block %p for thread %" Px "\n",
global_block_, OSThread::CurrentCurrentThreadIdAsIntPtr());
}
if ((global_block_ != NULL) && global_block_->IsFull()) {
// Global block is full.
global_block_->Finish();
global_block_ = NULL;
}
if (global_block_ == NULL) {
// Allocate a new block.
global_block_ = GetNewBlockLocked(NULL);
ASSERT(global_block_ != NULL);
}
if (global_block_ != NULL) {
// NOTE: We are exiting this function with the recorder's lock held.
ASSERT(!global_block_->IsFull());
TimelineEvent* event = global_block_->StartEvent();
if (event != NULL) {
event->set_global_block(true);
}
return event;
}
// Drop lock here as no event is being handed out.
lock_.Unlock();
return NULL;
}
void TimelineEventRecorder::ThreadBlockCompleteEvent(TimelineEvent* event) {
if (event == NULL) {
return;
}
ASSERT(!event->global_block());
// Grab the current thread.
Thread* thread = Thread::Current();
ASSERT(thread != NULL);
ASSERT(thread->isolate() != NULL);
// This event came from the isolate's thread local block. Unlock the
// thread's block lock.
Mutex* thread_block_lock = thread->timeline_block_lock();
ASSERT(thread_block_lock != NULL);
thread_block_lock->Unlock();
}
void TimelineEventRecorder::GlobalBlockCompleteEvent(TimelineEvent* event) {
if (event == NULL) {
return;
}
ASSERT(event->global_block());
// This event came from the global block, unlock the recorder's lock now
// that the event is filled.
lock_.Unlock();
}
// Trims the ']' character.
static void TrimOutput(char* output,
intptr_t* output_length) {
ASSERT(output != NULL);
ASSERT(output_length != NULL);
ASSERT(*output_length >= 2);
// We expect the first character to be the opening of an array.
ASSERT(output[0] == '[');
// We expect the last character to be the closing of an array.
ASSERT(output[*output_length - 1] == ']');
// Skip the ].
*output_length -= 1;
}
void TimelineEventRecorder::WriteTo(const char* directory) {
Dart_FileOpenCallback file_open = Isolate::file_open_callback();
Dart_FileWriteCallback file_write = Isolate::file_write_callback();
Dart_FileCloseCallback file_close = Isolate::file_close_callback();
if ((file_open == NULL) || (file_write == NULL) || (file_close == NULL)) {
return;
}
Thread* T = Thread::Current();
StackZone zone(T);
Timeline::ReclaimAllBlocks();
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(const_cast<const char**>(&output), &output_length);
TrimOutput(output, &output_length);
ASSERT(output_length >= 1);
(*file_write)(output, output_length, file);
// Free the stolen output.
free(output);
const char* dart_events =
DartTimelineEventIterator::PrintTraceEvents(this,
zone.GetZone(),
NULL);
// If we wrote out vm events and have dart events, write out the comma.
if ((output_length > 1) && (dart_events != NULL)) {
// Write out the ',' character.
const char* comma = ",";
(*file_write)(comma, 1, file);
}
// Write out the Dart events.
if (dart_events != NULL) {
(*file_write)(dart_events, strlen(dart_events), file);
}
// Write out the ']' character.
const char* array_close = "]";
(*file_write)(array_close, 1, file);
(*file_close)(file);
return;
}
void TimelineEventRecorder::ReclaimGlobalBlock() {
MutexLocker ml(&lock_);
if (global_block_ != NULL) {
global_block_->Finish();
global_block_ = NULL;
}
}
int64_t TimelineEventRecorder::GetNextAsyncId() {
// TODO(johnmccutchan): Gracefully handle wrap around.
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(Isolate::Current());
}
TimelineEventRingRecorder::TimelineEventRingRecorder(intptr_t capacity)
: blocks_(NULL),
capacity_(capacity),
num_blocks_(0),
block_cursor_(0),
dart_events_(NULL),
dart_events_capacity_(capacity),
dart_events_cursor_(0) {
// Capacity must be a multiple of TimelineEventBlock::kBlockSize
ASSERT((capacity % TimelineEventBlock::kBlockSize) == 0);
// Allocate blocks array.
num_blocks_ = capacity / TimelineEventBlock::kBlockSize;
blocks_ =
reinterpret_cast<TimelineEventBlock**>(
calloc(num_blocks_, sizeof(TimelineEventBlock*)));
// Allocate each block.
for (intptr_t i = 0; i < num_blocks_; i++) {
blocks_[i] = new TimelineEventBlock(i);
}
// Chain blocks together.
for (intptr_t i = 0; i < num_blocks_ - 1; i++) {
blocks_[i]->set_next(blocks_[i + 1]);
}
// Pre-allocate DartTimelineEvents.
dart_events_ =
reinterpret_cast<DartTimelineEvent**>(
calloc(dart_events_capacity_, sizeof(DartTimelineEvent*)));
for (intptr_t i = 0; i < dart_events_capacity_; i++) {
dart_events_[i] = new DartTimelineEvent();
}
}
TimelineEventRingRecorder::~TimelineEventRingRecorder() {
// Delete all blocks.
for (intptr_t i = 0; i < num_blocks_; i++) {
TimelineEventBlock* block = blocks_[i];
delete block;
}
free(blocks_);
// Delete all DartTimelineEvents.
for (intptr_t i = 0; i < dart_events_capacity_; i++) {
DartTimelineEvent* event = dart_events_[i];
delete event;
}
free(dart_events_);
}
void TimelineEventRingRecorder::PrintJSONEvents(
JSONArray* events,
TimelineEventFilter* filter) const {
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)) {
events->AddValue(event);
}
}
}
}
void TimelineEventRingRecorder::PrintJSON(JSONStream* js,
TimelineEventFilter* filter) {
MutexLocker ml(&lock_);
JSONObject topLevel(js);
topLevel.AddProperty("type", "_Timeline");
{
JSONArray events(&topLevel, "traceEvents");
PrintJSONMeta(&events);
PrintJSONEvents(&events, filter);
}
}
void TimelineEventRingRecorder::AppendDartEvent(Isolate* isolate,
const char* event) {
MutexLocker ml(&lock_);
// TODO(johnmccutchan): If locking becomes an issue, use the Isolate to store
// the events.
if (dart_events_cursor_ == dart_events_capacity_) {
dart_events_cursor_ = 0;
}
ASSERT(dart_events_[dart_events_cursor_] != NULL);
dart_events_[dart_events_cursor_]->Clear();
dart_events_[dart_events_cursor_]->Init(isolate, event);
dart_events_cursor_++;
}
intptr_t TimelineEventRingRecorder::NumDartEventsLocked() {
return dart_events_capacity_;
}
DartTimelineEvent* TimelineEventRingRecorder::DartEventAtLocked(intptr_t i) {
ASSERT(i >= 0);
ASSERT(i < dart_events_capacity_);
return dart_events_[i];
}
void TimelineEventRingRecorder::PrintTraceEvent(JSONStream* js,
TimelineEventFilter* filter) {
JSONArray events(js);
PrintJSONEvents(&events, filter);
}
TimelineEventBlock* TimelineEventRingRecorder::GetHeadBlockLocked() {
return blocks_[0];
}
TimelineEventBlock* TimelineEventRingRecorder::GetNewBlockLocked(
Isolate* isolate) {
// 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(isolate);
return block;
}
intptr_t TimelineEventRingRecorder::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* TimelineEventRingRecorder::StartEvent() {
// Grab the current thread.
Thread* thread = Thread::Current();
ASSERT(thread != NULL);
if (thread->isolate() == NULL) {
// Non-isolate thread case. This should be infrequent.
return GlobalBlockStartEvent();
}
return ThreadBlockStartEvent();
}
void TimelineEventRingRecorder::CompleteEvent(TimelineEvent* event) {
if (event == NULL) {
return;
}
if (event->global_block()) {
GlobalBlockCompleteEvent(event);
} else {
ThreadBlockCompleteEvent(event);
}
}
TimelineEventStreamingRecorder::TimelineEventStreamingRecorder() {
}
TimelineEventStreamingRecorder::~TimelineEventStreamingRecorder() {
}
void TimelineEventStreamingRecorder::PrintJSON(JSONStream* js,
TimelineEventFilter* filter) {
JSONObject topLevel(js);
topLevel.AddProperty("type", "_Timeline");
{
JSONArray events(&topLevel, "traceEvents");
PrintJSONMeta(&events);
}
}
void TimelineEventStreamingRecorder::PrintTraceEvent(
JSONStream* js,
TimelineEventFilter* filter) {
JSONArray events(js);
}
void TimelineEventStreamingRecorder::AppendDartEvent(Isolate* isolate,
const char* event) {
if (event != NULL) {
StreamDartEvent(event);
}
}
intptr_t TimelineEventStreamingRecorder::NumDartEventsLocked() {
return 0;
}
DartTimelineEvent* TimelineEventStreamingRecorder::DartEventAtLocked(
intptr_t i) {
return NULL;
}
TimelineEvent* TimelineEventStreamingRecorder::StartEvent() {
TimelineEvent* event = new TimelineEvent();
return event;
}
void TimelineEventStreamingRecorder::CompleteEvent(TimelineEvent* event) {
StreamEvent(event);
delete event;
}
TimelineEventEndlessRecorder::TimelineEventEndlessRecorder()
: head_(NULL),
block_index_(0),
dart_events_(NULL),
dart_events_capacity_(0),
dart_events_cursor_(0) {
}
void TimelineEventEndlessRecorder::PrintJSON(JSONStream* js,
TimelineEventFilter* filter) {
MutexLocker ml(&lock_);
JSONObject topLevel(js);
topLevel.AddProperty("type", "_Timeline");
{
JSONArray events(&topLevel, "traceEvents");
PrintJSONMeta(&events);
PrintJSONEvents(&events, filter);
}
}
void TimelineEventEndlessRecorder::PrintTraceEvent(
JSONStream* js,
TimelineEventFilter* filter) {
JSONArray events(js);
PrintJSONEvents(&events, filter);
}
void TimelineEventEndlessRecorder::AppendDartEvent(Isolate* isolate,
const char* event) {
MutexLocker ml(&lock_);
// TODO(johnmccutchan): If locking becomes an issue, use the Isolate to store
// the events.
if (dart_events_cursor_ == dart_events_capacity_) {
// Grow.
intptr_t new_capacity =
(dart_events_capacity_ == 0) ? 16 : dart_events_capacity_ * 2;
dart_events_ = reinterpret_cast<DartTimelineEvent**>(
realloc(dart_events_, new_capacity * sizeof(DartTimelineEvent*)));
for (intptr_t i = dart_events_capacity_; i < new_capacity; i++) {
// Fill with NULLs.
dart_events_[i] = NULL;
}
dart_events_capacity_ = new_capacity;
}
ASSERT(dart_events_cursor_ < dart_events_capacity_);
DartTimelineEvent* dart_event = new DartTimelineEvent();
dart_event->Init(isolate, event);
ASSERT(dart_events_[dart_events_cursor_] == NULL);
dart_events_[dart_events_cursor_++] = dart_event;
}
TimelineEventBlock* TimelineEventEndlessRecorder::GetHeadBlockLocked() {
return head_;
}
TimelineEvent* TimelineEventEndlessRecorder::StartEvent() {
// Grab the current thread.
Thread* thread = Thread::Current();
ASSERT(thread != NULL);
if (thread->isolate() == NULL) {
// Non-isolate thread case. This should be infrequent.
return GlobalBlockStartEvent();
}
return ThreadBlockStartEvent();
}
void TimelineEventEndlessRecorder::CompleteEvent(TimelineEvent* event) {
if (event == NULL) {
return;
}
if (event->global_block()) {
GlobalBlockCompleteEvent(event);
} else {
ThreadBlockCompleteEvent(event);
}
}
TimelineEventBlock* TimelineEventEndlessRecorder::GetNewBlockLocked(
Isolate* isolate) {
TimelineEventBlock* block = new TimelineEventBlock(block_index_++);
block->set_next(head_);
block->Open(isolate);
head_ = block;
if (FLAG_trace_timeline) {
if (isolate != NULL) {
OS::Print("Created new isolate block %p for %s\n",
block, isolate->name());
} else {
OS::Print("Created new global block %p\n", block);
}
}
return head_;
}
intptr_t TimelineEventEndlessRecorder::NumDartEventsLocked() {
return dart_events_cursor_;
}
DartTimelineEvent* TimelineEventEndlessRecorder::DartEventAtLocked(
intptr_t i) {
ASSERT(i >= 0);
ASSERT(i < dart_events_cursor_);
return dart_events_[i];
}
void TimelineEventEndlessRecorder::PrintJSONEvents(
JSONArray* events,
TimelineEventFilter* filter) const {
TimelineEventBlock* current = head_;
while (current != NULL) {
if (!filter->IncludeBlock(current)) {
current = current->next();
continue;
}
intptr_t length = current->length();
for (intptr_t i = 0; i < length; i++) {
TimelineEvent* event = current->At(i);
if (!filter->IncludeEvent(event)) {
continue;
}
events->AddValue(event);
}
current = current->next();
}
}
void TimelineEventEndlessRecorder::Clear() {
TimelineEventBlock* current = head_;
while (current != NULL) {
TimelineEventBlock* next = current->next();
delete current;
current = next;
}
head_ = NULL;
block_index_ = 0;
Thread* thread = Thread::Current();
thread->set_timeline_block(NULL);
}
TimelineEventBlock::TimelineEventBlock(intptr_t block_index)
: next_(NULL),
length_(0),
block_index_(block_index),
isolate_(NULL),
in_use_(false) {
}
TimelineEventBlock::~TimelineEventBlock() {
Reset();
}
TimelineEvent* TimelineEventBlock::StartEvent() {
ASSERT(!IsFull());
if (FLAG_trace_timeline) {
OS::Print("StartEvent in block %p for thread %" Px "\n",
this, OSThread::CurrentCurrentThreadIdAsIntPtr());
}
return &events_[length_++];
}
ThreadId TimelineEventBlock::thread() const {
ASSERT(length_ > 0);
return events_[0].thread();
}
int64_t TimelineEventBlock::LowerTimeBound() const {
ASSERT(length_ > 0);
return events_[0].TimeOrigin();
}
bool TimelineEventBlock::CheckBlock() {
if (length() == 0) {
return true;
}
// - events in the block come from one thread.
ThreadId tid = thread();
for (intptr_t i = 0; i < length(); i++) {
if (At(i)->thread() != tid) {
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;
isolate_ = NULL;
in_use_ = false;
}
void TimelineEventBlock::Open(Isolate* isolate) {
isolate_ = isolate;
in_use_ = true;
}
void TimelineEventBlock::Finish() {
if (FLAG_trace_timeline) {
OS::Print("Finish block %p\n", this);
}
in_use_ = false;
}
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;
}
DartTimelineEventIterator::DartTimelineEventIterator(
TimelineEventRecorder* recorder)
: cursor_(0),
num_events_(0),
recorder_(NULL) {
Reset(recorder);
}
DartTimelineEventIterator::~DartTimelineEventIterator() {
Reset(NULL);
}
void DartTimelineEventIterator::Reset(TimelineEventRecorder* recorder) {
// Clear state.
cursor_ = 0;
num_events_ = 0;
if (recorder_ != NULL) {
// Unlock old recorder.
recorder_->lock_.Unlock();
}
recorder_ = recorder;
if (recorder_ == NULL) {
return;
}
// Lock new recorder.
recorder_->lock_.Lock();
cursor_ = 0;
num_events_ = recorder_->NumDartEventsLocked();
}
bool DartTimelineEventIterator::HasNext() const {
return cursor_ < num_events_;
}
DartTimelineEvent* DartTimelineEventIterator::Next() {
ASSERT(cursor_ < num_events_);
DartTimelineEvent* r = recorder_->DartEventAtLocked(cursor_);
cursor_++;
return r;
}
const char* DartTimelineEventIterator::PrintTraceEvents(
TimelineEventRecorder* recorder,
Zone* zone,
Isolate* isolate) {
if (recorder == NULL) {
return NULL;
}
if (zone == NULL) {
return NULL;
}
char* result = NULL;
DartTimelineEventIterator iterator(recorder);
while (iterator.HasNext()) {
DartTimelineEvent* event = iterator.Next();
if (!event->IsValid()) {
// Skip invalid
continue;
}
if ((isolate != NULL) && (isolate != event->isolate())) {
// If an isolate was specified, skip events from other isolates.
continue;
}
ASSERT(event->event_as_json() != NULL);
result = zone->ConcatStrings(result, event->event_as_json());
}
return result;
}
} // namespace dart