| 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 | // for details. All rights reserved. Use of this source code is governed by a |
| 3 | // BSD-style license that can be found in the LICENSE file. |
| 4 | |
| 5 | #include "vm/timeline_analysis.h" |
| 6 | |
| 7 | #include "vm/flags.h" |
| 8 | #include "vm/isolate.h" |
| 9 | #include "vm/log.h" |
| 10 | #include "vm/os_thread.h" |
| 11 | |
| 12 | namespace dart { |
| 13 | |
| 14 | #ifndef PRODUCT |
| 15 | |
| 16 | DECLARE_FLAG(bool, trace_timeline_analysis); |
| 17 | DECLARE_FLAG(bool, timing); |
| 18 | |
| 19 | TimelineAnalysisThread::TimelineAnalysisThread(ThreadId id) : id_(id) {} |
| 20 | |
| 21 | TimelineAnalysisThread::~TimelineAnalysisThread() {} |
| 22 | |
| 23 | void TimelineAnalysisThread::AddBlock(TimelineEventBlock* block) { |
| 24 | blocks_.Add(block); |
| 25 | } |
| 26 | |
| 27 | static int CompareBlocksLowerTimeBound(TimelineEventBlock* const* a, |
| 28 | TimelineEventBlock* const* b) { |
| 29 | ASSERT(a != NULL); |
| 30 | ASSERT(*a != NULL); |
| 31 | ASSERT(b != NULL); |
| 32 | ASSERT(*b != NULL); |
| 33 | return (*a)->LowerTimeBound() - (*b)->LowerTimeBound(); |
| 34 | } |
| 35 | |
| 36 | void TimelineAnalysisThread::Finalize() { |
| 37 | blocks_.Sort(CompareBlocksLowerTimeBound); |
| 38 | if (FLAG_trace_timeline_analysis) { |
| 39 | THR_Print("Thread %" Pd " has %" Pd " blocks\n" , |
| 40 | OSThread::ThreadIdToIntPtr(id_), blocks_.length()); |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | TimelineAnalysisThreadEventIterator::TimelineAnalysisThreadEventIterator( |
| 45 | TimelineAnalysisThread* thread) { |
| 46 | Reset(thread); |
| 47 | } |
| 48 | |
| 49 | TimelineAnalysisThreadEventIterator::~TimelineAnalysisThreadEventIterator() { |
| 50 | Reset(NULL); |
| 51 | } |
| 52 | |
| 53 | void TimelineAnalysisThreadEventIterator::Reset( |
| 54 | TimelineAnalysisThread* thread) { |
| 55 | current_ = NULL; |
| 56 | thread_ = thread; |
| 57 | block_cursor_ = 0; |
| 58 | event_cursor_ = 0; |
| 59 | if (thread_ == NULL) { |
| 60 | return; |
| 61 | } |
| 62 | if (thread_->NumBlocks() == 0) { |
| 63 | return; |
| 64 | } |
| 65 | TimelineEventBlock* block = thread_->At(block_cursor_); |
| 66 | ASSERT(!block->IsEmpty()); |
| 67 | current_ = block->At(event_cursor_++); |
| 68 | } |
| 69 | |
| 70 | bool TimelineAnalysisThreadEventIterator::HasNext() const { |
| 71 | return current_ != NULL; |
| 72 | } |
| 73 | |
| 74 | TimelineEvent* TimelineAnalysisThreadEventIterator::Next() { |
| 75 | ASSERT(current_ != NULL); |
| 76 | TimelineEvent* r = current_; |
| 77 | current_ = NULL; |
| 78 | |
| 79 | TimelineEventBlock* block = thread_->At(block_cursor_); |
| 80 | if (event_cursor_ == block->length()) { |
| 81 | // Reached the end of this block, move to the next. |
| 82 | block_cursor_++; |
| 83 | if (block_cursor_ == thread_->NumBlocks()) { |
| 84 | // Exhausted our supply of blocks. |
| 85 | return r; |
| 86 | } |
| 87 | // Grab next block. |
| 88 | block = thread_->At(block_cursor_); |
| 89 | event_cursor_ = 0; |
| 90 | ASSERT(!block->IsEmpty()); |
| 91 | } |
| 92 | current_ = block->At(event_cursor_++); |
| 93 | return r; |
| 94 | } |
| 95 | |
| 96 | TimelineAnalysis::TimelineAnalysis(Zone* zone, |
| 97 | Isolate* isolate, |
| 98 | TimelineEventRecorder* recorder) |
| 99 | : zone_(zone), |
| 100 | isolate_(isolate), |
| 101 | recorder_(recorder), |
| 102 | has_error_(false), |
| 103 | error_msg_(NULL) { |
| 104 | ASSERT(zone_ != NULL); |
| 105 | ASSERT(isolate_ != NULL); |
| 106 | ASSERT(recorder_ != NULL); |
| 107 | } |
| 108 | |
| 109 | TimelineAnalysis::~TimelineAnalysis() {} |
| 110 | |
| 111 | void TimelineAnalysis::BuildThreads() { |
| 112 | DiscoverThreads(); |
| 113 | FinalizeThreads(); |
| 114 | } |
| 115 | |
| 116 | TimelineAnalysisThread* TimelineAnalysis::GetThread(ThreadId tid) { |
| 117 | // Linear lookup because we expect N (# of threads in an isolate) to be small. |
| 118 | for (intptr_t i = 0; i < threads_.length(); i++) { |
| 119 | TimelineAnalysisThread* thread = threads_.At(i); |
| 120 | ASSERT(thread != NULL); |
| 121 | if (thread->id() == tid) { |
| 122 | return thread; |
| 123 | } |
| 124 | } |
| 125 | return NULL; |
| 126 | } |
| 127 | |
| 128 | TimelineAnalysisThread* TimelineAnalysis::GetOrAddThread(ThreadId tid) { |
| 129 | TimelineAnalysisThread* thread = GetThread(tid); |
| 130 | if (thread != NULL) { |
| 131 | return thread; |
| 132 | } |
| 133 | // New thread. |
| 134 | thread = new TimelineAnalysisThread(tid); |
| 135 | threads_.Add(thread); |
| 136 | return thread; |
| 137 | } |
| 138 | |
| 139 | void TimelineAnalysis::DiscoverThreads() { |
| 140 | TimelineEventBlockIterator it(recorder_); |
| 141 | while (it.HasNext()) { |
| 142 | TimelineEventBlock* block = it.Next(); |
| 143 | ASSERT(block != NULL); |
| 144 | if (block->IsEmpty()) { |
| 145 | // Skip empty blocks. |
| 146 | continue; |
| 147 | } |
| 148 | if (!block->CheckBlock()) { |
| 149 | if (FLAG_trace_timeline_analysis) { |
| 150 | THR_Print("DiscoverThreads block %" Pd |
| 151 | " " |
| 152 | "violates invariants.\n" , |
| 153 | block->block_index()); |
| 154 | } |
| 155 | SetError("Block %" Pd |
| 156 | " violates invariants. See " |
| 157 | "TimelineEventBlock::CheckBlock" , |
| 158 | block->block_index()); |
| 159 | return; |
| 160 | } |
| 161 | TimelineAnalysisThread* thread = GetOrAddThread(block->thread_id()); |
| 162 | ASSERT(thread != NULL); |
| 163 | thread->AddBlock(block); |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | void TimelineAnalysis::FinalizeThreads() { |
| 168 | for (intptr_t i = 0; i < threads_.length(); i++) { |
| 169 | TimelineAnalysisThread* thread = threads_.At(i); |
| 170 | ASSERT(thread != NULL); |
| 171 | thread->Finalize(); |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | void TimelineAnalysis::SetError(const char* format, ...) { |
| 176 | ASSERT(!has_error_); |
| 177 | ASSERT(error_msg_ == NULL); |
| 178 | has_error_ = true; |
| 179 | va_list args; |
| 180 | va_start(args, format); |
| 181 | error_msg_ = zone_->VPrint(format, args); |
| 182 | ASSERT(error_msg_ != NULL); |
| 183 | if (FLAG_trace_timeline_analysis) { |
| 184 | OS::PrintErr("TimelineAnalysis error = %s\n" , error_msg_); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | TimelineLabelPauseInfo::TimelineLabelPauseInfo(const char* name) |
| 189 | : name_(name), |
| 190 | inclusive_micros_(0), |
| 191 | exclusive_micros_(0), |
| 192 | max_inclusive_micros_(0), |
| 193 | max_exclusive_micros_(0) { |
| 194 | ASSERT(name_ != NULL); |
| 195 | } |
| 196 | |
| 197 | void TimelineLabelPauseInfo::OnPush(int64_t micros, bool already_on_stack) { |
| 198 | UpdateInclusiveMicros(micros, already_on_stack); |
| 199 | } |
| 200 | |
| 201 | void TimelineLabelPauseInfo::OnPop(int64_t exclusive_micros) { |
| 202 | UpdateExclusiveMicros(exclusive_micros); |
| 203 | } |
| 204 | |
| 205 | void TimelineLabelPauseInfo::OnBeginPop(int64_t inclusive_micros, |
| 206 | int64_t exclusive_micros, |
| 207 | bool already_on_stack) { |
| 208 | UpdateInclusiveMicros(inclusive_micros, already_on_stack); |
| 209 | UpdateExclusiveMicros(exclusive_micros); |
| 210 | } |
| 211 | |
| 212 | void TimelineLabelPauseInfo::UpdateInclusiveMicros(int64_t inclusive_micros, |
| 213 | bool already_on_stack) { |
| 214 | if (!already_on_stack) { |
| 215 | // Only adjust inclusive counts if we aren't already on the stack. |
| 216 | add_inclusive_micros(inclusive_micros); |
| 217 | if (inclusive_micros > max_inclusive_micros_) { |
| 218 | max_inclusive_micros_ = inclusive_micros; |
| 219 | } |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | void TimelineLabelPauseInfo::UpdateExclusiveMicros(int64_t exclusive_micros) { |
| 224 | add_exclusive_micros(exclusive_micros); |
| 225 | if (exclusive_micros > max_exclusive_micros_) { |
| 226 | max_exclusive_micros_ = exclusive_micros; |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | void TimelineLabelPauseInfo::Aggregate( |
| 231 | const TimelineLabelPauseInfo* thread_pause_info) { |
| 232 | ASSERT(thread_pause_info != NULL); |
| 233 | inclusive_micros_ += thread_pause_info->inclusive_micros_; |
| 234 | exclusive_micros_ += thread_pause_info->exclusive_micros_; |
| 235 | if (max_inclusive_micros_ < thread_pause_info->max_inclusive_micros_) { |
| 236 | max_inclusive_micros_ = thread_pause_info->max_inclusive_micros_; |
| 237 | } |
| 238 | if (max_exclusive_micros_ < thread_pause_info->max_exclusive_micros_) { |
| 239 | max_exclusive_micros_ = thread_pause_info->max_exclusive_micros_; |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | TimelinePauses::TimelinePauses(Zone* zone, |
| 244 | Isolate* isolate, |
| 245 | TimelineEventRecorder* recorder) |
| 246 | : TimelineAnalysis(zone, isolate, recorder) {} |
| 247 | |
| 248 | void TimelinePauses::Setup() { |
| 249 | BuildThreads(); |
| 250 | } |
| 251 | |
| 252 | void TimelinePauses::CalculatePauseTimesForThread(ThreadId tid) { |
| 253 | if (has_error()) { |
| 254 | return; |
| 255 | } |
| 256 | TimelineAnalysisThread* thread = GetThread(tid); |
| 257 | if (thread == NULL) { |
| 258 | SetError("Thread %" Pd " does not exist." , OSThread::ThreadIdToIntPtr(tid)); |
| 259 | return; |
| 260 | } |
| 261 | ProcessThread(thread); |
| 262 | } |
| 263 | |
| 264 | TimelineLabelPauseInfo* TimelinePauses::GetLabelPauseInfo( |
| 265 | const char* name) const { |
| 266 | ASSERT(name != NULL); |
| 267 | // Linear lookup because we expect N (# of labels in an isolate) to be small. |
| 268 | for (intptr_t i = 0; i < labels_.length(); i++) { |
| 269 | TimelineLabelPauseInfo* label = labels_.At(i); |
| 270 | if (strcmp(label->name(), name) == 0) { |
| 271 | return label; |
| 272 | } |
| 273 | } |
| 274 | return NULL; |
| 275 | } |
| 276 | |
| 277 | int64_t TimelinePauses::InclusiveTime(const char* name) const { |
| 278 | TimelineLabelPauseInfo* pause_info = GetLabelPauseInfo(name); |
| 279 | ASSERT(pause_info != NULL); |
| 280 | return pause_info->inclusive_micros(); |
| 281 | } |
| 282 | |
| 283 | int64_t TimelinePauses::ExclusiveTime(const char* name) const { |
| 284 | TimelineLabelPauseInfo* pause_info = GetLabelPauseInfo(name); |
| 285 | ASSERT(pause_info != NULL); |
| 286 | return pause_info->exclusive_micros(); |
| 287 | } |
| 288 | |
| 289 | int64_t TimelinePauses::MaxInclusiveTime(const char* name) const { |
| 290 | TimelineLabelPauseInfo* pause_info = GetLabelPauseInfo(name); |
| 291 | ASSERT(pause_info != NULL); |
| 292 | return pause_info->max_inclusive_micros(); |
| 293 | } |
| 294 | |
| 295 | int64_t TimelinePauses::MaxExclusiveTime(const char* name) const { |
| 296 | TimelineLabelPauseInfo* pause_info = GetLabelPauseInfo(name); |
| 297 | ASSERT(pause_info != NULL); |
| 298 | return pause_info->max_exclusive_micros(); |
| 299 | } |
| 300 | |
| 301 | void TimelinePauses::ProcessThread(TimelineAnalysisThread* thread) { |
| 302 | ASSERT(thread != NULL); |
| 303 | stack_.Clear(); |
| 304 | labels_.Clear(); |
| 305 | |
| 306 | TimelineAnalysisThreadEventIterator it(thread); |
| 307 | if (FLAG_trace_timeline_analysis) { |
| 308 | THR_Print(">>> TimelinePauses::ProcessThread %" Pd "\n" , |
| 309 | OSThread::ThreadIdToIntPtr(thread->id())); |
| 310 | } |
| 311 | intptr_t event_count = 0; |
| 312 | while (!has_error() && it.HasNext()) { |
| 313 | TimelineEvent* event = it.Next(); |
| 314 | if (event->isolate_id() != isolate_->main_port()) { |
| 315 | // Skip events that do not belong to the isolate. |
| 316 | continue; |
| 317 | } |
| 318 | if (event->IsFinishedDuration()) { |
| 319 | int64_t start = event->TimeOrigin(); |
| 320 | PopFinishedDurations(start); |
| 321 | if (!CheckStack(event)) { |
| 322 | SetError("Duration check fail." ); |
| 323 | return; |
| 324 | } |
| 325 | event_count++; |
| 326 | Push(event); |
| 327 | } else if (event->IsBeginOrEnd()) { |
| 328 | event_count++; |
| 329 | if (event->IsBegin()) { |
| 330 | PopFinishedDurations(event->TimeOrigin()); |
| 331 | Push(event); |
| 332 | } else { |
| 333 | ASSERT(event->IsEnd()); |
| 334 | PopFinishedDurations(event->TimeOrigin()); |
| 335 | PopBegin(event->label(), event->TimeOrigin()); |
| 336 | } |
| 337 | } else { |
| 338 | // Skip other event kinds. |
| 339 | } |
| 340 | } |
| 341 | // Pop remaining duration stack. |
| 342 | PopFinishedDurations(kMaxInt64); |
| 343 | if (FLAG_trace_timeline_analysis) { |
| 344 | THR_Print("<<< TimelinePauses::ProcessThread %" Pd " had %" Pd " events\n" , |
| 345 | OSThread::ThreadIdToIntPtr(thread->id()), event_count); |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | // Verify that |event| is contained within all parent events on the stack. |
| 350 | bool TimelinePauses::CheckStack(TimelineEvent* event) { |
| 351 | ASSERT(event != NULL); |
| 352 | for (intptr_t i = 0; i < stack_.length(); i++) { |
| 353 | const StackItem& slot = stack_.At(i); |
| 354 | if (slot.event->IsDuration()) { |
| 355 | if (!slot.event->DurationContains(event)) { |
| 356 | return false; |
| 357 | } |
| 358 | } else { |
| 359 | ASSERT(slot.event->IsBegin()); |
| 360 | if (slot.event->TimeOrigin() > event->TimeOrigin()) { |
| 361 | return false; |
| 362 | } |
| 363 | } |
| 364 | } |
| 365 | return true; |
| 366 | } |
| 367 | |
| 368 | void TimelinePauses::PopFinishedDurations(int64_t start) { |
| 369 | while (stack_.length() > 0) { |
| 370 | const StackItem& top = stack_.Last(); |
| 371 | if (top.event->IsDuration() && top.event->DurationFinishedBefore(start)) { |
| 372 | top.pause_info->OnPop(top.exclusive_micros); |
| 373 | // Top of stack completes before |start|. |
| 374 | stack_.RemoveLast(); |
| 375 | if (FLAG_trace_timeline_analysis) { |
| 376 | THR_Print("Popping %s (%" Pd64 " <= %" Pd64 ")\n" , top.event->label(), |
| 377 | top.event->TimeEnd(), start); |
| 378 | } |
| 379 | } else { |
| 380 | return; |
| 381 | } |
| 382 | } |
| 383 | } |
| 384 | |
| 385 | void TimelinePauses::PopBegin(const char* label, int64_t end) { |
| 386 | if (stack_.length() == 0) { |
| 387 | SetError("PopBegin(%s, ...) called with empty stack." , label); |
| 388 | return; |
| 389 | } |
| 390 | ASSERT(stack_.length() > 0); |
| 391 | const StackItem& top = stack_.Last(); |
| 392 | const char* top_label = top.event->label(); |
| 393 | const bool top_is_begin = top.event->IsBegin(); |
| 394 | const int64_t start = top.event->TimeOrigin(); |
| 395 | if (start > end) { |
| 396 | SetError("Bad time stamps for PopBegin(%s, ...) %" Pd64 " > %" Pd64 "" , |
| 397 | label, start, end); |
| 398 | return; |
| 399 | } |
| 400 | const int64_t duration = end - start; |
| 401 | // Sanity checks. |
| 402 | if (strcmp(top_label, label) != 0) { |
| 403 | SetError("PopBegin(%s, ...) called with %s at the top of stack" , label, |
| 404 | top.event->label()); |
| 405 | return; |
| 406 | } |
| 407 | if (!top_is_begin) { |
| 408 | SetError("kEnd event not paired with kBegin event for label %s" , label); |
| 409 | return; |
| 410 | } |
| 411 | // Pop this event. |
| 412 | // Add duration to exclusive micros. |
| 413 | if (FLAG_trace_timeline_analysis) { |
| 414 | THR_Print("Popping %s (%" Pd64 ")\n" , top.event->label(), duration); |
| 415 | } |
| 416 | const int64_t exclusive_micros = top.exclusive_micros + duration; |
| 417 | stack_.RemoveLast(); |
| 418 | top.pause_info->OnBeginPop(duration, exclusive_micros, |
| 419 | IsLabelOnStack(top_label)); |
| 420 | if (StackDepth() > 0) { |
| 421 | StackItem& top = GetStackTop(); |
| 422 | // |top| is under the popped |event|'s shadow, adjust the exclusive micros. |
| 423 | top.exclusive_micros -= duration; |
| 424 | } |
| 425 | } |
| 426 | |
| 427 | void TimelinePauses::Push(TimelineEvent* event) { |
| 428 | TimelineLabelPauseInfo* pause_info = GetOrAddLabelPauseInfo(event->label()); |
| 429 | ASSERT(pause_info != NULL); |
| 430 | // |pause_info| will be running for |event->TimeDuration()|. |
| 431 | if (FLAG_trace_timeline_analysis) { |
| 432 | THR_Print("Pushing %s %" Pd64 " us\n" , pause_info->name(), |
| 433 | event->TimeDuration()); |
| 434 | } |
| 435 | if (event->IsDuration()) { |
| 436 | pause_info->OnPush(event->TimeDuration(), IsLabelOnStack(event->label())); |
| 437 | if (StackDepth() > 0) { |
| 438 | StackItem& top = GetStackTop(); |
| 439 | // |top| is under |event|'s shadow, adjust the exclusive micros. |
| 440 | top.exclusive_micros -= event->TimeDuration(); |
| 441 | } |
| 442 | // Push onto the stack. |
| 443 | StackItem item; |
| 444 | item.event = event; |
| 445 | item.pause_info = pause_info; |
| 446 | item.exclusive_micros = event->TimeDuration(); |
| 447 | stack_.Add(item); |
| 448 | } else { |
| 449 | ASSERT(event->IsBegin()); |
| 450 | pause_info->OnPush(0, IsLabelOnStack(event->label())); |
| 451 | // Push onto the stack. |
| 452 | StackItem item; |
| 453 | item.event = event; |
| 454 | item.pause_info = pause_info; |
| 455 | item.exclusive_micros = 0; |
| 456 | stack_.Add(item); |
| 457 | } |
| 458 | } |
| 459 | |
| 460 | bool TimelinePauses::IsLabelOnStack(const char* label) const { |
| 461 | ASSERT(label != NULL); |
| 462 | for (intptr_t i = 0; i < stack_.length(); i++) { |
| 463 | const StackItem& slot = stack_.At(i); |
| 464 | if (strcmp(slot.event->label(), label) == 0) { |
| 465 | return true; |
| 466 | } |
| 467 | } |
| 468 | return false; |
| 469 | } |
| 470 | |
| 471 | intptr_t TimelinePauses::StackDepth() const { |
| 472 | return stack_.length(); |
| 473 | } |
| 474 | |
| 475 | TimelinePauses::StackItem& TimelinePauses::GetStackTop() { |
| 476 | ASSERT(stack_.length() > 0); |
| 477 | return stack_.Last(); |
| 478 | } |
| 479 | |
| 480 | TimelineLabelPauseInfo* TimelinePauses::GetOrAddLabelPauseInfo( |
| 481 | const char* name) { |
| 482 | ASSERT(name != NULL); |
| 483 | TimelineLabelPauseInfo* pause_info = GetLabelPauseInfo(name); |
| 484 | if (pause_info != NULL) { |
| 485 | return pause_info; |
| 486 | } |
| 487 | // New label. |
| 488 | pause_info = new TimelineLabelPauseInfo(name); |
| 489 | labels_.Add(pause_info); |
| 490 | return pause_info; |
| 491 | } |
| 492 | |
| 493 | TimelinePauseTrace::TimelinePauseTrace() {} |
| 494 | |
| 495 | TimelinePauseTrace::~TimelinePauseTrace() {} |
| 496 | |
| 497 | void TimelinePauseTrace::Print() { |
| 498 | Thread* thread = Thread::Current(); |
| 499 | ASSERT(thread != NULL); |
| 500 | Isolate* isolate = thread->isolate(); |
| 501 | ASSERT(isolate != NULL); |
| 502 | Zone* zone = thread->zone(); |
| 503 | ASSERT(zone != NULL); |
| 504 | TimelineEventRecorder* recorder = Timeline::recorder(); |
| 505 | ASSERT(recorder != NULL); |
| 506 | TimelinePauses pauses(zone, isolate, recorder); |
| 507 | pauses.Setup(); |
| 508 | |
| 509 | THR_Print("Timing for isolate (%" Pd64 ") '%s' (from %" Pd " threads)\n" , |
| 510 | static_cast<int64_t>(isolate->main_port()), isolate->name(), |
| 511 | pauses.NumThreads()); |
| 512 | THR_Print("\n" ); |
| 513 | for (intptr_t t_idx = 0; t_idx < pauses.NumThreads(); t_idx++) { |
| 514 | TimelineAnalysisThread* tat = pauses.At(t_idx); |
| 515 | ASSERT(tat != NULL); |
| 516 | pauses.CalculatePauseTimesForThread(tat->id()); |
| 517 | THR_Print("Thread %" Pd " (%" Pd "):\n" , t_idx, |
| 518 | OSThread::ThreadIdToIntPtr(tat->id())); |
| 519 | for (intptr_t j = 0; j < pauses.NumPauseInfos(); j++) { |
| 520 | const TimelineLabelPauseInfo* pause_info = pauses.PauseInfoAt(j); |
| 521 | ASSERT(pause_info != NULL); |
| 522 | Aggregate(pause_info); |
| 523 | PrintPauseInfo(pause_info); |
| 524 | } |
| 525 | THR_Print("\n" ); |
| 526 | } |
| 527 | THR_Print("Totals:\n" ); |
| 528 | for (intptr_t i = 0; i < isolate_labels_.length(); i++) { |
| 529 | TimelineLabelPauseInfo* pause_info = isolate_labels_.At(i); |
| 530 | ASSERT(pause_info != NULL); |
| 531 | PrintPauseInfo(pause_info); |
| 532 | } |
| 533 | THR_Print("\n" ); |
| 534 | } |
| 535 | |
| 536 | TimelineLabelPauseInfo* TimelinePauseTrace::GetOrAddLabelPauseInfo( |
| 537 | const char* name) { |
| 538 | ASSERT(name != NULL); |
| 539 | // Linear lookup because we expect N (# of labels in an isolate) to be small. |
| 540 | for (intptr_t i = 0; i < isolate_labels_.length(); i++) { |
| 541 | TimelineLabelPauseInfo* label = isolate_labels_.At(i); |
| 542 | if (strcmp(label->name(), name) == 0) { |
| 543 | return label; |
| 544 | } |
| 545 | } |
| 546 | // New label. |
| 547 | TimelineLabelPauseInfo* pause_info = new TimelineLabelPauseInfo(name); |
| 548 | isolate_labels_.Add(pause_info); |
| 549 | return pause_info; |
| 550 | } |
| 551 | |
| 552 | void TimelinePauseTrace::Aggregate( |
| 553 | const TimelineLabelPauseInfo* thread_pause_info) { |
| 554 | ASSERT(thread_pause_info != NULL); |
| 555 | TimelineLabelPauseInfo* isolate_pause_info = |
| 556 | GetOrAddLabelPauseInfo(thread_pause_info->name()); |
| 557 | ASSERT(isolate_pause_info != NULL); |
| 558 | isolate_pause_info->Aggregate(thread_pause_info); |
| 559 | } |
| 560 | |
| 561 | void TimelinePauseTrace::PrintPauseInfo( |
| 562 | const TimelineLabelPauseInfo* pause_info) { |
| 563 | ASSERT(pause_info != NULL); |
| 564 | THR_Print("%s : " , pause_info->name()); |
| 565 | THR_Print("%.3f ms total on stack; " , |
| 566 | MicrosecondsToMilliseconds(pause_info->inclusive_micros())); |
| 567 | THR_Print("%.3f ms total executing; " , |
| 568 | MicrosecondsToMilliseconds(pause_info->exclusive_micros())); |
| 569 | THR_Print("%.3f ms max on stack; " , |
| 570 | MicrosecondsToMilliseconds(pause_info->max_inclusive_micros())); |
| 571 | THR_Print("%.3f ms max executing.\n" , |
| 572 | MicrosecondsToMilliseconds(pause_info->max_exclusive_micros())); |
| 573 | } |
| 574 | |
| 575 | #endif // !PRODUCT |
| 576 | |
| 577 | } // namespace dart |
| 578 | |