mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
![Ryan Macnak](/assets/img/avatar_default.png)
Makes it easier to match things up between the bot test failure output and core dumps. Change-Id: I8ed89dc67b0c46716d4bfed3ec1a7cdbc5411eea Reviewed-on: https://dart-review.googlesource.com/c/85466 Reviewed-by: Vyacheslav Egorov <vegorov@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
578 lines
17 KiB
C++
578 lines
17 KiB
C++
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#include "vm/timeline_analysis.h"
|
|
|
|
#include "vm/flags.h"
|
|
#include "vm/isolate.h"
|
|
#include "vm/log.h"
|
|
#include "vm/os_thread.h"
|
|
|
|
namespace dart {
|
|
|
|
#ifndef PRODUCT
|
|
|
|
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 %" Pd " 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->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_id());
|
|
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);
|
|
if (FLAG_trace_timeline_analysis) {
|
|
OS::PrintErr("TimelineAnalysis error = %s\n", error_msg_);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
UpdateInclusiveMicros(micros, already_on_stack);
|
|
}
|
|
|
|
void TimelineLabelPauseInfo::OnPop(int64_t exclusive_micros) {
|
|
UpdateExclusiveMicros(exclusive_micros);
|
|
}
|
|
|
|
void TimelineLabelPauseInfo::OnBeginPop(int64_t inclusive_micros,
|
|
int64_t exclusive_micros,
|
|
bool already_on_stack) {
|
|
UpdateInclusiveMicros(inclusive_micros, already_on_stack);
|
|
UpdateExclusiveMicros(exclusive_micros);
|
|
}
|
|
|
|
void TimelineLabelPauseInfo::UpdateInclusiveMicros(int64_t inclusive_micros,
|
|
bool already_on_stack) {
|
|
if (!already_on_stack) {
|
|
// Only adjust inclusive counts if we aren't already on the stack.
|
|
add_inclusive_micros(inclusive_micros);
|
|
if (inclusive_micros > max_inclusive_micros_) {
|
|
max_inclusive_micros_ = inclusive_micros;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TimelineLabelPauseInfo::UpdateExclusiveMicros(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 %" Pd " 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 %" Pd "\n",
|
|
OSThread::ThreadIdToIntPtr(thread->id()));
|
|
}
|
|
intptr_t event_count = 0;
|
|
while (!has_error() && it.HasNext()) {
|
|
TimelineEvent* event = it.Next();
|
|
if (event->isolate_id() != isolate_->main_port()) {
|
|
// Skip events that do not belong to the isolate.
|
|
continue;
|
|
}
|
|
if (event->IsFinishedDuration()) {
|
|
int64_t start = event->TimeOrigin();
|
|
PopFinishedDurations(start);
|
|
if (!CheckStack(event)) {
|
|
SetError("Duration check fail.");
|
|
return;
|
|
}
|
|
event_count++;
|
|
Push(event);
|
|
} else if (event->IsBeginOrEnd()) {
|
|
event_count++;
|
|
if (event->IsBegin()) {
|
|
PopFinishedDurations(event->TimeOrigin());
|
|
Push(event);
|
|
} else {
|
|
ASSERT(event->IsEnd());
|
|
PopFinishedDurations(event->TimeOrigin());
|
|
PopBegin(event->label(), event->TimeOrigin());
|
|
}
|
|
} else {
|
|
// Skip other event kinds.
|
|
}
|
|
}
|
|
// Pop remaining duration stack.
|
|
PopFinishedDurations(kMaxInt64);
|
|
if (FLAG_trace_timeline_analysis) {
|
|
THR_Print("<<< TimelinePauses::ProcessThread %" Pd " 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->IsDuration()) {
|
|
if (!slot.event->DurationContains(event)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
ASSERT(slot.event->IsBegin());
|
|
if (slot.event->TimeOrigin() > event->TimeOrigin()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void TimelinePauses::PopFinishedDurations(int64_t start) {
|
|
while (stack_.length() > 0) {
|
|
const StackItem& top = stack_.Last();
|
|
if (top.event->IsDuration() && 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::PopBegin(const char* label, int64_t end) {
|
|
if (stack_.length() == 0) {
|
|
SetError("PopBegin(%s, ...) called with empty stack.", label);
|
|
return;
|
|
}
|
|
ASSERT(stack_.length() > 0);
|
|
const StackItem& top = stack_.Last();
|
|
const char* top_label = top.event->label();
|
|
const bool top_is_begin = top.event->IsBegin();
|
|
const int64_t start = top.event->TimeOrigin();
|
|
if (start > end) {
|
|
SetError("Bad time stamps for PopBegin(%s, ...) %" Pd64 " > %" Pd64 "",
|
|
label, start, end);
|
|
return;
|
|
}
|
|
const int64_t duration = end - start;
|
|
// Sanity checks.
|
|
if (strcmp(top_label, label) != 0) {
|
|
SetError("PopBegin(%s, ...) called with %s at the top of stack", label,
|
|
top.event->label());
|
|
return;
|
|
}
|
|
if (!top_is_begin) {
|
|
SetError("kEnd event not paired with kBegin event for label %s", label);
|
|
return;
|
|
}
|
|
// Pop this event.
|
|
// Add duration to exclusive micros.
|
|
if (FLAG_trace_timeline_analysis) {
|
|
THR_Print("Popping %s (%" Pd64 ")\n", top.event->label(), duration);
|
|
}
|
|
const int64_t exclusive_micros = top.exclusive_micros + duration;
|
|
stack_.RemoveLast();
|
|
top.pause_info->OnBeginPop(duration, exclusive_micros,
|
|
IsLabelOnStack(top_label));
|
|
if (StackDepth() > 0) {
|
|
StackItem& top = GetStackTop();
|
|
// |top| is under the popped |event|'s shadow, adjust the exclusive micros.
|
|
top.exclusive_micros -= duration;
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
if (event->IsDuration()) {
|
|
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);
|
|
} else {
|
|
ASSERT(event->IsBegin());
|
|
pause_info->OnPush(0, IsLabelOnStack(event->label()));
|
|
// Push onto the stack.
|
|
StackItem item;
|
|
item.event = event;
|
|
item.pause_info = pause_info;
|
|
item.exclusive_micros = 0;
|
|
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 (%" Pd64 ") '%s' (from %" Pd " threads)\n",
|
|
static_cast<int64_t>(isolate->main_port()), 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 " (%" Pd "):\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()));
|
|
}
|
|
|
|
#endif // !PRODUCT
|
|
|
|
} // namespace dart
|