blob: 1ca8442a2d8ba32f7898839b04ce0774bb2e027c [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 "vm/timeline_analysis.h"
#include "vm/flags.h"
#include "vm/log.h"
#include "vm/os_thread.h"
namespace dart {
DECLARE_FLAG(bool, trace_timeline_analysis);
DECLARE_FLAG(bool, timing);
TimelineAnalysisThread::TimelineAnalysisThread(ThreadId id)
: id_(id) {
}
TimelineAnalysisThread::~TimelineAnalysisThread() {
}
void TimelineAnalysisThread::AddBlock(TimelineEventBlock* block) {
blocks_.Add(block);
}
static int CompareBlocksLowerTimeBound(TimelineEventBlock* const* a,
TimelineEventBlock* const* b) {
ASSERT(a != NULL);
ASSERT(*a != NULL);
ASSERT(b != NULL);
ASSERT(*b != NULL);
return (*a)->LowerTimeBound() - (*b)->LowerTimeBound();
}
void TimelineAnalysisThread::Finalize() {
blocks_.Sort(CompareBlocksLowerTimeBound);
if (FLAG_trace_timeline_analysis) {
THR_Print("Thread %" Px " has %" Pd " blocks\n",
OSThread::ThreadIdToIntPtr(id_),
blocks_.length());
}
}
TimelineAnalysisThreadEventIterator::TimelineAnalysisThreadEventIterator(
TimelineAnalysisThread* thread) {
Reset(thread);
}
TimelineAnalysisThreadEventIterator::~TimelineAnalysisThreadEventIterator() {
Reset(NULL);
}
void TimelineAnalysisThreadEventIterator::Reset(
TimelineAnalysisThread* thread) {
current_ = NULL;
thread_ = thread;
block_cursor_ = 0;
event_cursor_ = 0;
if (thread_ == NULL) {
return;
}
if (thread_->NumBlocks() == 0) {
return;
}
TimelineEventBlock* block = thread_->At(block_cursor_);
ASSERT(!block->IsEmpty());
current_ = block->At(event_cursor_++);
}
bool TimelineAnalysisThreadEventIterator::HasNext() const {
return current_ != NULL;
}
TimelineEvent* TimelineAnalysisThreadEventIterator::Next() {
ASSERT(current_ != NULL);
TimelineEvent* r = current_;
current_ = NULL;
TimelineEventBlock* block = thread_->At(block_cursor_);
if (event_cursor_ == block->length()) {
// Reached the end of this block, move to the next.
block_cursor_++;
if (block_cursor_ == thread_->NumBlocks()) {
// Exhausted our supply of blocks.
return r;
}
// Grab next block.
block = thread_->At(block_cursor_);
event_cursor_ = 0;
ASSERT(!block->IsEmpty());
}
current_ = block->At(event_cursor_++);
return r;
}
TimelineAnalysis::TimelineAnalysis(Zone* zone,
Isolate* isolate,
TimelineEventRecorder* recorder)
: zone_(zone),
isolate_(isolate),
recorder_(recorder),
has_error_(false),
error_msg_(NULL) {
ASSERT(zone_ != NULL);
ASSERT(isolate_ != NULL);
ASSERT(recorder_ != NULL);
}
TimelineAnalysis::~TimelineAnalysis() {
}
void TimelineAnalysis::BuildThreads() {
DiscoverThreads();
FinalizeThreads();
}
TimelineAnalysisThread* TimelineAnalysis::GetThread(ThreadId tid) {
// Linear lookup because we expect N (# of threads in an isolate) to be small.
for (intptr_t i = 0; i < threads_.length(); i++) {
TimelineAnalysisThread* thread = threads_.At(i);
ASSERT(thread != NULL);
if (thread->id() == tid) {
return thread;
}
}
return NULL;
}
TimelineAnalysisThread* TimelineAnalysis::GetOrAddThread(ThreadId tid) {
TimelineAnalysisThread* thread = GetThread(tid);
if (thread != NULL) {
return thread;
}
// New thread.
thread = new TimelineAnalysisThread(tid);
threads_.Add(thread);
return thread;
}
void TimelineAnalysis::DiscoverThreads() {
TimelineEventBlockIterator it(recorder_);
while (it.HasNext()) {
TimelineEventBlock* block = it.Next();
ASSERT(block != NULL);
if (block->IsEmpty()) {
// Skip empty blocks.
continue;
}
if (block->isolate() != isolate_) {
// Skip blocks for other isolates.
continue;
}
if (!block->CheckBlock()) {
if (FLAG_trace_timeline_analysis) {
THR_Print("DiscoverThreads block %" Pd " "
"violates invariants.\n", block->block_index());
}
SetError("Block %" Pd " violates invariants. See "
"TimelineEventBlock::CheckBlock", block->block_index());
return;
}
TimelineAnalysisThread* thread = GetOrAddThread(block->thread());
ASSERT(thread != NULL);
thread->AddBlock(block);
}
}
void TimelineAnalysis::FinalizeThreads() {
for (intptr_t i = 0; i < threads_.length(); i++) {
TimelineAnalysisThread* thread = threads_.At(i);
ASSERT(thread != NULL);
thread->Finalize();
}
}
void TimelineAnalysis::SetError(const char* format, ...) {
ASSERT(!has_error_);
ASSERT(error_msg_ == NULL);
has_error_ = true;
va_list args;
va_start(args, format);
error_msg_ = zone_->VPrint(format, args);
ASSERT(error_msg_ != NULL);
}
TimelineLabelPauseInfo::TimelineLabelPauseInfo(const char* name)
: name_(name),
inclusive_micros_(0),
exclusive_micros_(0),
max_inclusive_micros_(0),
max_exclusive_micros_(0) {
ASSERT(name_ != NULL);
}
void TimelineLabelPauseInfo::OnPush(int64_t micros, bool already_on_stack) {
if (!already_on_stack) {
// Only adjust inclusive counts if we aren't already on the stack.
add_inclusive_micros(micros);
if (micros > max_inclusive_micros_) {
max_inclusive_micros_ = micros;
}
}
}
void TimelineLabelPauseInfo::OnPop(int64_t exclusive_micros) {
add_exclusive_micros(exclusive_micros);
if (exclusive_micros > max_exclusive_micros_) {
max_exclusive_micros_ = exclusive_micros;
}
}
void TimelineLabelPauseInfo::Aggregate(
const TimelineLabelPauseInfo* thread_pause_info) {
ASSERT(thread_pause_info != NULL);
inclusive_micros_ += thread_pause_info->inclusive_micros_;
exclusive_micros_ += thread_pause_info->exclusive_micros_;
if (max_inclusive_micros_ < thread_pause_info->max_inclusive_micros_) {
max_inclusive_micros_ = thread_pause_info->max_inclusive_micros_;
}
if (max_exclusive_micros_ < thread_pause_info->max_exclusive_micros_) {
max_exclusive_micros_ = thread_pause_info->max_exclusive_micros_;
}
}
TimelinePauses::TimelinePauses(Zone* zone,
Isolate* isolate,
TimelineEventRecorder* recorder)
: TimelineAnalysis(zone, isolate, recorder) {
}
void TimelinePauses::Setup() {
BuildThreads();
}
void TimelinePauses::CalculatePauseTimesForThread(ThreadId tid) {
if (has_error()) {
return;
}
TimelineAnalysisThread* thread = GetThread(tid);
if (thread == NULL) {
SetError("Thread %" Px " does not exist.", OSThread::ThreadIdToIntPtr(tid));
return;
}
ProcessThread(thread);
}
TimelineLabelPauseInfo* TimelinePauses::GetLabelPauseInfo(
const char* name) const {
ASSERT(name != NULL);
// Linear lookup because we expect N (# of labels in an isolate) to be small.
for (intptr_t i = 0; i < labels_.length(); i++) {
TimelineLabelPauseInfo* label = labels_.At(i);
if (strcmp(label->name(), name) == 0) {
return label;
}
}
return NULL;
}
int64_t TimelinePauses::InclusiveTime(const char* name) const {
TimelineLabelPauseInfo* pause_info = GetLabelPauseInfo(name);
ASSERT(pause_info != NULL);
return pause_info->inclusive_micros();
}
int64_t TimelinePauses::ExclusiveTime(const char* name) const {
TimelineLabelPauseInfo* pause_info = GetLabelPauseInfo(name);
ASSERT(pause_info != NULL);
return pause_info->exclusive_micros();
}
int64_t TimelinePauses::MaxInclusiveTime(const char* name) const {
TimelineLabelPauseInfo* pause_info = GetLabelPauseInfo(name);
ASSERT(pause_info != NULL);
return pause_info->max_inclusive_micros();
}
int64_t TimelinePauses::MaxExclusiveTime(const char* name) const {
TimelineLabelPauseInfo* pause_info = GetLabelPauseInfo(name);
ASSERT(pause_info != NULL);
return pause_info->max_exclusive_micros();
}
void TimelinePauses::ProcessThread(TimelineAnalysisThread* thread) {
ASSERT(thread != NULL);
stack_.Clear();
labels_.Clear();
TimelineAnalysisThreadEventIterator it(thread);
if (FLAG_trace_timeline_analysis) {
THR_Print(">>> TimelinePauses::ProcessThread %" Px "\n",
OSThread::ThreadIdToIntPtr(thread->id()));
}
intptr_t event_count = 0;
while (it.HasNext()) {
TimelineEvent* event = it.Next();
if (!event->IsFinishedDuration()) {
// We only care about finished duration events.
continue;
}
int64_t start = event->TimeOrigin();
PopFinished(start);
if (!CheckStack(event)) {
SetError("Duration check fail.");
return;
}
event_count++;
Push(event);
}
// Pop remaining stack.
PopFinished(kMaxInt64);
if (FLAG_trace_timeline_analysis) {
THR_Print("<<< TimelinePauses::ProcessThread %" Px " had %" Pd " events\n",
OSThread::ThreadIdToIntPtr(thread->id()),
event_count);
}
}
// Verify that |event| is contained within all parent events on the stack.
bool TimelinePauses::CheckStack(TimelineEvent* event) {
ASSERT(event != NULL);
for (intptr_t i = 0; i < stack_.length(); i++) {
const StackItem& slot = stack_.At(i);
if (!slot.event->DurationContains(event)) {
return false;
}
}
return true;
}
void TimelinePauses::PopFinished(int64_t start) {
while (stack_.length() > 0) {
const StackItem& top = stack_.Last();
if (top.event->DurationFinishedBefore(start)) {
top.pause_info->OnPop(top.exclusive_micros);
// Top of stack completes before |start|.
stack_.RemoveLast();
if (FLAG_trace_timeline_analysis) {
THR_Print("Popping %s (%" Pd64 " <= %" Pd64 ")\n",
top.event->label(),
top.event->TimeEnd(),
start);
}
} else {
return;
}
}
}
void TimelinePauses::Push(TimelineEvent* event) {
TimelineLabelPauseInfo* pause_info = GetOrAddLabelPauseInfo(event->label());
ASSERT(pause_info != NULL);
// |pause_info| will be running for |event->TimeDuration()|.
if (FLAG_trace_timeline_analysis) {
THR_Print("Pushing %s %" Pd64 " us\n",
pause_info->name(),
event->TimeDuration());
}
pause_info->OnPush(event->TimeDuration(), IsLabelOnStack(event->label()));
if (StackDepth() > 0) {
StackItem& top = GetStackTop();
// |top| is under |event|'s shadow, adjust the exclusive micros.
top.exclusive_micros -= event->TimeDuration();
}
// Push onto the stack.
StackItem item;
item.event = event;
item.pause_info = pause_info;
item.exclusive_micros = event->TimeDuration();
stack_.Add(item);
}
bool TimelinePauses::IsLabelOnStack(const char* label) const {
ASSERT(label != NULL);
for (intptr_t i = 0; i < stack_.length(); i++) {
const StackItem& slot = stack_.At(i);
if (strcmp(slot.event->label(), label) == 0) {
return true;
}
}
return false;
}
intptr_t TimelinePauses::StackDepth() const {
return stack_.length();
}
TimelinePauses::StackItem& TimelinePauses::GetStackTop() {
ASSERT(stack_.length() > 0);
return stack_.Last();
}
TimelineLabelPauseInfo* TimelinePauses::GetOrAddLabelPauseInfo(
const char* name) {
ASSERT(name != NULL);
TimelineLabelPauseInfo* pause_info = GetLabelPauseInfo(name);
if (pause_info != NULL) {
return pause_info;
}
// New label.
pause_info = new TimelineLabelPauseInfo(name);
labels_.Add(pause_info);
return pause_info;
}
TimelinePauseTrace::TimelinePauseTrace() {
}
TimelinePauseTrace::~TimelinePauseTrace() {
}
void TimelinePauseTrace::Print() {
Thread* thread = Thread::Current();
ASSERT(thread != NULL);
Isolate* isolate = thread->isolate();
ASSERT(isolate != NULL);
Zone* zone = thread->zone();
ASSERT(zone != NULL);
TimelineEventRecorder* recorder = Timeline::recorder();
ASSERT(recorder != NULL);
TimelinePauses pauses(zone, isolate, recorder);
pauses.Setup();
THR_Print("Timing for isolate %s (from %" Pd " threads)\n",
isolate->name(),
pauses.NumThreads());
THR_Print("\n");
for (intptr_t t_idx = 0; t_idx < pauses.NumThreads(); t_idx++) {
TimelineAnalysisThread* tat = pauses.At(t_idx);
ASSERT(tat != NULL);
pauses.CalculatePauseTimesForThread(tat->id());
THR_Print("Thread %" Pd " (%" Px "):\n",
t_idx,
OSThread::ThreadIdToIntPtr(tat->id()));
for (intptr_t j = 0; j < pauses.NumPauseInfos(); j++) {
const TimelineLabelPauseInfo* pause_info = pauses.PauseInfoAt(j);
ASSERT(pause_info != NULL);
Aggregate(pause_info);
PrintPauseInfo(pause_info);
}
THR_Print("\n");
}
THR_Print("Totals:\n");
for (intptr_t i = 0; i < isolate_labels_.length(); i++) {
TimelineLabelPauseInfo* pause_info = isolate_labels_.At(i);
ASSERT(pause_info != NULL);
PrintPauseInfo(pause_info);
}
THR_Print("\n");
}
TimelineLabelPauseInfo* TimelinePauseTrace::GetOrAddLabelPauseInfo(
const char* name) {
ASSERT(name != NULL);
// Linear lookup because we expect N (# of labels in an isolate) to be small.
for (intptr_t i = 0; i < isolate_labels_.length(); i++) {
TimelineLabelPauseInfo* label = isolate_labels_.At(i);
if (strcmp(label->name(), name) == 0) {
return label;
}
}
// New label.
TimelineLabelPauseInfo* pause_info = new TimelineLabelPauseInfo(name);
isolate_labels_.Add(pause_info);
return pause_info;
}
void TimelinePauseTrace::Aggregate(
const TimelineLabelPauseInfo* thread_pause_info) {
ASSERT(thread_pause_info != NULL);
TimelineLabelPauseInfo* isolate_pause_info =
GetOrAddLabelPauseInfo(thread_pause_info->name());
ASSERT(isolate_pause_info != NULL);
isolate_pause_info->Aggregate(thread_pause_info);
}
void TimelinePauseTrace::PrintPauseInfo(
const TimelineLabelPauseInfo* pause_info) {
ASSERT(pause_info != NULL);
THR_Print("%s : ", pause_info->name());
THR_Print("%.3f ms total on stack; ",
MicrosecondsToMilliseconds(pause_info->inclusive_micros()));
THR_Print("%.3f ms total executing; ",
MicrosecondsToMilliseconds(pause_info->exclusive_micros()));
THR_Print("%.3f ms max on stack; ",
MicrosecondsToMilliseconds(pause_info->max_inclusive_micros()));
THR_Print("%.3f ms max executing.\n",
MicrosecondsToMilliseconds(pause_info->max_exclusive_micros()));
}
} // namespace dart