Ticket #4056: Profiler2.2.patch
File Profiler2.2.patch, 47.7 KB (added by , 8 years ago) |
---|
-
source/ps/Profiler2.cpp
31 31 #include "third_party/mongoose/mongoose.h" 32 32 33 33 #include <iomanip> 34 #include <unordered_map> 34 35 35 36 CProfiler2 g_Profiler2; 36 37 … … 80 81 std::stringstream stream; 81 82 82 83 std::string uri = request_info->uri; 83 if (uri == "/overview") 84 85 if (uri == "/download") 84 86 { 87 profiler->SaveToFile(); 88 } 89 else if (uri == "/overview") 90 { 85 91 profiler->ConstructJSONOverview(stream); 86 92 } 87 93 else if (uri == "/query") … … 191 197 void CProfiler2::ShutDownHTTP() 192 198 { 193 199 LOGMESSAGERENDER("Shutting down profiler2 HTTP server"); 200 194 201 if (m_MgContext) 195 202 { 196 203 mg_stop(m_MgContext); … … 219 226 220 227 ENSURE(!m_GPU); // must shutdown GPU before profiler 221 228 229 SaveToFile(); 230 222 231 if (m_MgContext) 223 232 { 224 233 mg_stop(m_MgContext); … … 301 310 } 302 311 303 312 CProfiler2::ThreadStorage::ThreadStorage(CProfiler2& profiler, const std::string& name) : 304 m_Profiler(profiler), m_Name(name), m_BufferPos0(0), m_BufferPos1(0), m_LastTime(timer_Time())313 m_Profiler(profiler), m_Name(name), m_BufferPos0(0), m_BufferPos1(0), m_LastTime(timer_Time()), m_HeldDepth(0) 305 314 { 306 315 m_Buffer = new u8[BUFFER_SIZE]; 307 316 memset(m_Buffer, ITEM_NOP, BUFFER_SIZE); … … 312 321 delete[] m_Buffer; 313 322 } 314 323 324 void CProfiler2::ThreadStorage::Write(EItem type, const void* item, u32 itemSize) 325 { 326 if (m_HeldDepth > 0) 327 { 328 WriteHold(type, item, itemSize); 329 return; 330 } 331 // See m_BufferPos0 etc for comments on synchronisation 332 333 u32 size = 1 + itemSize; 334 u32 start = m_BufferPos0; 335 if (start + size > BUFFER_SIZE) 336 { 337 // The remainder of the buffer is too small - fill the rest 338 // with NOPs then start from offset 0, so we don't have to 339 // bother splitting the real item across the end of the buffer 340 341 m_BufferPos0 = size; 342 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer 343 344 memset(m_Buffer + start, 0, BUFFER_SIZE - start); 345 start = 0; 346 } 347 else 348 { 349 m_BufferPos0 = start + size; 350 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer 351 } 352 353 m_Buffer[start] = (u8)type; 354 memcpy(&m_Buffer[start + 1], item, itemSize); 355 356 COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer 357 m_BufferPos1 = start + size; 358 } 359 360 void CProfiler2::ThreadStorage::WriteHold(EItem type, const void* item, u32 itemSize) 361 { 362 u32 size = 1 + itemSize; 363 364 if (m_HoldBuffers[m_HeldDepth - 1].pos + size > CProfiler2::HOLD_BUFFER_SIZE) 365 return; // we held on too much data, ignore the rest 366 367 m_HoldBuffers[m_HeldDepth - 1].buffer[m_HoldBuffers[m_HeldDepth - 1].pos] = (u8)type; 368 memcpy(&m_HoldBuffers[m_HeldDepth - 1].buffer[m_HoldBuffers[m_HeldDepth - 1].pos + 1], item, itemSize); 369 370 m_HoldBuffers[m_HeldDepth - 1].pos += size; 371 } 372 315 373 std::string CProfiler2::ThreadStorage::GetBuffer() 316 374 { 317 375 // Called from an arbitrary thread (not the one writing to the buffer). … … 355 413 Write(ITEM_ATTRIBUTE, buffer, 4 + len); 356 414 } 357 415 416 size_t CProfiler2::ThreadStorage::HoldLevel() 417 { 418 return m_HeldDepth; 419 } 358 420 421 u8 CProfiler2::ThreadStorage::HoldType() 422 { 423 return m_HoldBuffers[m_HeldDepth - 1].type; 424 } 425 426 void CProfiler2::ThreadStorage::PutOnHold(u8 newType) 427 { 428 m_HeldDepth++; 429 m_HoldBuffers[m_HeldDepth - 1].clear(); 430 m_HoldBuffers[m_HeldDepth - 1].setType(newType); 431 } 432 433 // this flattens the stack, use it sensibly 434 void rewriteBuffer(u8* buffer, u32& bufferSize) 435 { 436 double startTime = timer_Time(); 437 438 u32 size = bufferSize; 439 u32 readPos = 0; 440 441 double initialTime = -1; 442 double total_time = -1; 443 const char* regionName; 444 std::set<std::string> topLevelArgs; 445 446 typedef std::tuple<const char*, double, std::set<std::string> > infoPerType; 447 std::unordered_map<std::string, infoPerType> timeByType; 448 std::vector<double> last_time_stack; 449 std::vector<const char*> last_names; 450 451 // never too many hacks 452 std::string current_attribute = ""; 453 std::map<std::string, double> time_per_attribute; 454 455 // Let's read the first event 456 { 457 u8 type = buffer[readPos]; 458 ++readPos; 459 if (type != CProfiler2::ITEM_ENTER) 460 { 461 debug_warn("Profiler2: Condensing a region should run into ITEM_ENTER first"); 462 return; // do nothing 463 } 464 CProfiler2::SItem_dt_id item; 465 memcpy(&item, buffer + readPos, sizeof(item)); 466 readPos += sizeof(item); 467 468 regionName = item.id; 469 last_names.push_back(item.id); 470 initialTime = (double)item.dt; 471 } 472 int enter = 1; 473 int leaves = 0; 474 // Read subsequent events. Flatten hierarchy because it would get too complicated otherwise. 475 // To make sure time doesn't bloat, subtract time from nested events 476 while (readPos < size) 477 { 478 u8 type = buffer[readPos]; 479 ++readPos; 480 481 switch (type) 482 { 483 case CProfiler2::ITEM_NOP: 484 { 485 // ignore 486 break; 487 } 488 case CProfiler2::ITEM_SYNC: 489 { 490 debug_warn("Aggregated regions should not be used across frames"); 491 // still try to act sane 492 readPos += sizeof(double); 493 readPos += sizeof(CProfiler2::RESYNC_MAGIC); 494 break; 495 } 496 case CProfiler2::ITEM_EVENT: 497 { 498 // skip for now 499 readPos += sizeof(CProfiler2::SItem_dt_id); 500 break; 501 } 502 case CProfiler2::ITEM_ENTER: 503 { 504 enter++; 505 CProfiler2::SItem_dt_id item; 506 memcpy(&item, buffer + readPos, sizeof(item)); 507 readPos += sizeof(item); 508 last_time_stack.push_back((double)item.dt); 509 last_names.push_back(item.id); 510 current_attribute = ""; 511 break; 512 } 513 case CProfiler2::ITEM_LEAVE: 514 { 515 CProfiler2::SItem_dt_id item; 516 memcpy(&item, buffer + readPos, sizeof(item)); 517 readPos += sizeof(item); 518 519 leaves++; 520 if (last_names.empty()) 521 { 522 // we somehow lost the first entry in the process 523 debug_warn("alert %i %i %li", readPos, enter, holdLevel); 524 } 525 526 last_names.pop_back(); 527 528 if (last_time_stack.empty()) 529 { 530 // this is the leave for the whole scope 531 total_time = item.dt; 532 break; 533 } 534 double time = (double)item.dt - last_time_stack.back(); 535 536 std::string name = std::string(item.id); 537 auto TimeForType = timeByType.find(name); 538 if (TimeForType == timeByType.end()) 539 { 540 // keep reference to the original char pointer to make sure we don't break things down the line 541 std::get<0>(timeByType[name]) = item.id; 542 std::get<1>(timeByType[name]) = 0; 543 } 544 std::get<1>(timeByType[name]) += time; 545 546 last_time_stack.pop_back(); 547 // if we were nested, subtract our time from the below scope by making it look like it starts later 548 if (!last_time_stack.empty()) 549 last_time_stack.back() += time; 550 551 if (!current_attribute.empty()) 552 { 553 time_per_attribute[current_attribute] += time; 554 } 555 556 break; 557 } 558 case CProfiler2::ITEM_ATTRIBUTE: 559 { 560 // skip for now 561 u32 len; 562 memcpy(&len, buffer + readPos, sizeof(len)); 563 ENSURE(len <= CProfiler2::MAX_ATTRIBUTE_LENGTH); 564 readPos += sizeof(len); 565 566 char message[CProfiler2::MAX_ATTRIBUTE_LENGTH] = {0}; 567 memcpy(&message[0], buffer + readPos, len); 568 CStr mess = CStr((const char*)message, len); 569 if (!last_names.empty()) 570 { 571 auto it = timeByType.find(std::string(last_names.back())); 572 if (it == timeByType.end()) 573 topLevelArgs.insert(mess); 574 else 575 std::get<2>(timeByType[std::string(last_names.back())]).insert(mess); 576 } 577 readPos += len; 578 current_attribute = mess; 579 break; 580 } 581 default: 582 debug_warn(L"Invalid profiler item when parsing buffer"); 583 continue; 584 } 585 } 586 587 // rewrite the buffer 588 // what we rewrite will always be smaller than the current buffer's size 589 u32 writePos = 0; 590 double curTime = initialTime; 591 // the region enter 592 { 593 CProfiler2::SItem_dt_id item = { curTime, regionName }; 594 buffer[writePos] = (u8)CProfiler2::ITEM_ENTER; 595 memcpy(buffer + writePos + 1, &item, sizeof(item)); 596 writePos += sizeof(item) + 1; 597 // add a nanosecond for sanity 598 curTime += 0.000001; 599 } 600 // sub-events, aggregated 601 for (auto& type : timeByType) 602 { 603 CProfiler2::SItem_dt_id item = { curTime, std::get<0>(type.second) }; 604 buffer[writePos] = (u8)CProfiler2::ITEM_ENTER; 605 memcpy(buffer + writePos + 1, &item, sizeof(item)); 606 writePos += sizeof(item) + 1; 607 608 // write relevant attributes if present 609 for (const auto& attrib : std::get<2>(type.second)) 610 { 611 buffer[writePos] = (u8)CProfiler2::ITEM_ATTRIBUTE; 612 writePos++; 613 std::string basic = attrib; 614 auto time_attrib = time_per_attribute.find(attrib); 615 if (time_attrib != time_per_attribute.end()) 616 basic += " " + CStr::FromInt(1000000*time_attrib->second) + "us"; 617 618 u32 length = basic.size(); 619 memcpy(buffer + writePos, &length, sizeof(length)); 620 writePos += sizeof(length); 621 memcpy(buffer + writePos, basic.c_str(), length); 622 writePos += length; 623 } 624 625 curTime += std::get<1>(type.second); 626 item.dt = curTime; 627 buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE; 628 memcpy(buffer + writePos + 1, &item, sizeof(item)); 629 writePos += sizeof(item) + 1; 630 } 631 // the region leave 632 { 633 if (total_time < 0) 634 { 635 total_time = curTime + 0.000001; 636 637 buffer[writePos] = (u8)CProfiler2::ITEM_ATTRIBUTE; 638 writePos++; 639 u32 length = sizeof("buffer overflow"); 640 memcpy(buffer + writePos, &length, sizeof(length)); 641 writePos += sizeof(length); 642 memcpy(buffer + writePos, "buffer overflow", length); 643 writePos += length; 644 } 645 else if (total_time < curTime) 646 { 647 // this seems to happen on rare occasions. 648 curTime = total_time; 649 } 650 CProfiler2::SItem_dt_id item = { total_time, regionName }; 651 buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE; 652 memcpy(buffer + writePos + 1, &item, sizeof(item)); 653 writePos += sizeof(item) + 1; 654 } 655 // Time of computation 656 { 657 CProfiler2::SItem_dt_id item = { curTime, "CondenseBuffer" }; 658 buffer[writePos] = (u8)CProfiler2::ITEM_ENTER; 659 memcpy(buffer + writePos + 1, &item, sizeof(item)); 660 writePos += sizeof(item) + 1; 661 } 662 { 663 CProfiler2::SItem_dt_id item = { curTime + timer_Time() - startTime, "CondenseBuffer" }; 664 buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE; 665 memcpy(buffer + writePos + 1, &item, sizeof(item)); 666 writePos += sizeof(item) + 1; 667 // add a nanosecond for sanity 668 curTime += 0.000001; 669 } 670 bufferSize = writePos; 671 } 672 673 void CProfiler2::ThreadStorage::HoldToBuffer(bool condensed) 674 { 675 ENSURE(m_HeldDepth); 676 if (condensed) 677 { 678 // rewrite the buffer to show aggregated data 679 rewriteBuffer(m_HoldBuffers[m_HeldDepth - 1].buffer, m_HoldBuffers[m_HeldDepth - 1].pos); 680 } 681 682 if (m_HeldDepth > 1) 683 { 684 // copy onto buffer below 685 HoldBuffer& copied = m_HoldBuffers[m_HeldDepth - 1]; 686 HoldBuffer& target = m_HoldBuffers[m_HeldDepth - 2]; 687 if (target.pos + copied.pos > HOLD_BUFFER_SIZE) 688 return; // too much data, too bad 689 690 memcpy(&target.buffer[target.pos], copied.buffer, copied.pos); 691 692 target.pos += copied.pos; 693 } 694 else 695 { 696 u32 size = m_HoldBuffers[m_HeldDepth - 1].pos; 697 u32 start = m_BufferPos0; 698 if (start + size > BUFFER_SIZE) 699 { 700 m_BufferPos0 = size; 701 COMPILER_FENCE; 702 memset(m_Buffer + start, 0, BUFFER_SIZE - start); 703 start = 0; 704 } 705 else 706 { 707 m_BufferPos0 = start + size; 708 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer 709 } 710 memcpy(&m_Buffer[start], m_HoldBuffers[m_HeldDepth - 1].buffer, size); 711 COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer 712 m_BufferPos1 = start + size; 713 } 714 m_HeldDepth--; 715 } 716 void CProfiler2::ThreadStorage::ThrowawayHoldBuffer() 717 { 718 if (!m_HeldDepth) 719 return; 720 m_HeldDepth--; 721 } 722 359 723 void CProfiler2::ConstructJSONOverview(std::ostream& stream) 360 724 { 361 725 TIMER(L"profile2 overview"); … … 438 802 pos += sizeof(item); 439 803 if (lastTime >= 0) 440 804 { 441 lastTime = lastTime + (double)item.dt;442 visitor.OnEvent(lastTime , item.id);805 //lastTime = lastTime + (double)item.dt; 806 visitor.OnEvent(lastTime + (double)item.dt, item.id); 443 807 } 444 808 break; 445 809 } … … 450 814 pos += sizeof(item); 451 815 if (lastTime >= 0) 452 816 { 453 lastTime = lastTime + (double)item.dt;454 visitor.OnEnter(lastTime , item.id);817 //lastTime = lastTime + (double)item.dt; 818 visitor.OnEnter(lastTime + (double)item.dt, item.id); 455 819 } 456 820 break; 457 821 } … … 462 826 pos += sizeof(item); 463 827 if (lastTime >= 0) 464 828 { 465 lastTime = lastTime + (double)item.dt;466 visitor.OnLeave(lastTime , item.id);829 //lastTime = lastTime + (double)item.dt; 830 visitor.OnLeave(lastTime + (double)item.dt, item.id); 467 831 } 468 832 break; 469 833 } … … 522 886 void OnLeave(double time, const char* id) 523 887 { 524 888 m_Stream << "[3," << std::fixed << std::setprecision(9) << time; 889 525 890 m_Stream << ",\"" << CStr(id).EscapeToPrintableASCII() << "\"],\n"; 526 891 } 527 892 … … 596 961 } 597 962 stream << "\n]});\n"; 598 963 } 964 965 CProfile2SpikeRegion::CProfile2SpikeRegion(const char* name, double spikeLimit) : 966 m_Name(name), m_Limit(spikeLimit), m_PushedHold(true) 967 { 968 if (g_Profiler2.HoldLevel() < 8 && g_Profiler2.HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE) 969 g_Profiler2.HoldMessages(CProfiler2::ThreadStorage::BUFFER_SPIKE); 970 else 971 m_PushedHold = false; 972 COMPILER_FENCE; 973 g_Profiler2.RecordRegionEnter(m_Name); 974 m_StartTime = g_Profiler2.GetTime(); 975 } 976 CProfile2SpikeRegion::~CProfile2SpikeRegion() 977 { 978 double time = g_Profiler2.GetTime(); 979 g_Profiler2.RecordRegionLeave(m_Name); 980 bool shouldWrite = time - m_StartTime > m_Limit; 981 982 if (m_PushedHold) 983 g_Profiler2.StopHoldingMessages(shouldWrite); 984 } 985 986 CProfile2AggregatedRegion::CProfile2AggregatedRegion(const char* name, double spikeLimit) : 987 m_Name(name), m_Limit(spikeLimit), m_PushedHold(true) 988 { 989 if (g_Profiler2.HoldLevel() < 8 && g_Profiler2.HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE) 990 g_Profiler2.HoldMessages(CProfiler2::ThreadStorage::BUFFER_AGGREGATE); 991 else 992 m_PushedHold = false; 993 COMPILER_FENCE; 994 g_Profiler2.RecordRegionEnter(m_Name); 995 m_StartTime = g_Profiler2.GetTime(); 996 } 997 CProfile2AggregatedRegion::~CProfile2AggregatedRegion() 998 { 999 double time = g_Profiler2.GetTime(); 1000 g_Profiler2.RecordRegionLeave(m_Name); 1001 bool shouldWrite = time - m_StartTime > m_Limit; 1002 1003 if (m_PushedHold) 1004 g_Profiler2.StopHoldingMessages(shouldWrite, true); 1005 } -
source/ps/Profiler2.h
91 91 class CProfiler2 92 92 { 93 93 friend class CProfiler2GPU_base; 94 94 friend class CProfile2SpikeRegion; 95 friend class CProfile2AggregatedRegion; 95 96 public: 96 97 // Items stored in the buffers: 97 98 … … 123 124 private: 124 125 // TODO: what's a good size? 125 126 // TODO: different threads might want different sizes 126 static const size_t BUFFER_SIZE = 1024*1024; 127 static const size_t BUFFER_SIZE = 10*1024*1024; 128 static const size_t HOLD_BUFFER_SIZE = 128 * 1024; 127 129 128 130 /** 129 131 * Class instantiated in every registered thread. … … 135 137 ThreadStorage(CProfiler2& profiler, const std::string& name); 136 138 ~ThreadStorage(); 137 139 140 enum { BUFFER_NORMAL, BUFFER_SPIKE, BUFFER_AGGREGATE }; 141 138 142 void RecordSyncMarker(double t) 139 143 { 140 144 // Store the magic string followed by the absolute time … … 153 157 // (to save memory) without suffering from precision problems 154 158 SItem_dt_id item = { (float)(t - m_LastTime), id }; 155 159 Write(type, &item, sizeof(item)); 156 m_LastTime = t;160 //m_LastTime = t; 157 161 } 158 162 159 163 void RecordFrameStart(double t) … … 172 176 va_end(argp); 173 177 } 174 178 179 size_t HoldLevel(); 180 u8 HoldType(); 181 void PutOnHold(u8 type); 182 void HoldToBuffer(bool condensed); 183 void ThrowawayHoldBuffer(); 184 175 185 CProfiler2& GetProfiler() 176 186 { 177 187 return m_Profiler; … … 193 203 /** 194 204 * Store an item into the buffer. 195 205 */ 196 void Write(EItem type, const void* item, u32 itemSize) 197 { 198 // See m_BufferPos0 etc for comments on synchronisation 206 void Write(EItem type, const void* item, u32 itemSize); 199 207 200 u32 size = 1 + itemSize; 201 u32 start = m_BufferPos0; 202 if (start + size > BUFFER_SIZE) 203 { 204 // The remainder of the buffer is too small - fill the rest 205 // with NOPs then start from offset 0, so we don't have to 206 // bother splitting the real item across the end of the buffer 208 void WriteHold(EItem type, const void* item, u32 itemSize); 207 209 208 m_BufferPos0 = size;209 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer210 211 memset(m_Buffer + start, 0, BUFFER_SIZE - start);212 start = 0;213 }214 else215 {216 m_BufferPos0 = start + size;217 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer218 }219 220 m_Buffer[start] = (u8)type;221 memcpy(&m_Buffer[start + 1], item, itemSize);222 223 COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer224 m_BufferPos1 = start + size;225 }226 227 210 CProfiler2& m_Profiler; 228 211 std::string m_Name; 229 212 … … 231 214 232 215 u8* m_Buffer; 233 216 217 struct HoldBuffer 218 { 219 friend class ThreadStorage; 220 public: 221 HoldBuffer() 222 { 223 buffer = new u8[HOLD_BUFFER_SIZE]; 224 memset(buffer, ITEM_NOP, HOLD_BUFFER_SIZE); 225 pos = 0; 226 } 227 ~HoldBuffer() 228 { 229 delete[] buffer; 230 } 231 void clear() 232 { 233 pos = 0; 234 } 235 void setType(u8 newType) 236 { 237 type = newType; 238 } 239 u8* buffer; 240 u32 pos; 241 u8 type; 242 }; 243 244 HoldBuffer m_HoldBuffers[8]; 245 size_t m_HeldDepth; 246 234 247 // To allow hopefully-safe reading of the buffer from a separate thread, 235 248 // without any expensive synchronisation in the recording thread, 236 249 // two copies of the current buffer write position are stored. … … 327 340 GetThreadStorage().Record(ITEM_ENTER, GetTime(), id); 328 341 } 329 342 343 void RecordRegionEnter(const char* id, double time) 344 { 345 GetThreadStorage().Record(ITEM_ENTER, time, id); 346 } 347 330 348 void RecordRegionLeave(const char* id) 331 349 { 332 350 GetThreadStorage().Record(ITEM_LEAVE, GetTime(), id); … … 346 364 void RecordGPURegionLeave(const char* id); 347 365 348 366 /** 367 * Hold onto messages until a call to release or write the held messages. 368 */ 369 size_t HoldLevel() 370 { 371 return GetThreadStorage().HoldLevel(); 372 } 373 374 u8 HoldType() 375 { 376 return GetThreadStorage().HoldType(); 377 } 378 379 void HoldMessages(u8 type) 380 { 381 GetThreadStorage().PutOnHold(type); 382 } 383 384 void StopHoldingMessages(bool writeToBuffer, bool condensed = false) 385 { 386 if (writeToBuffer) 387 GetThreadStorage().HoldToBuffer(condensed); 388 else 389 GetThreadStorage().ThrowawayHoldBuffer(); 390 } 391 392 /** 349 393 * Call in any thread to produce a JSON representation of the general 350 394 * state of the application. 351 395 */ … … 424 468 { 425 469 g_Profiler2.RecordRegionLeave(m_Name); 426 470 } 471 protected: 472 const char* m_Name; 473 }; 474 475 /** 476 * Scope-based enter/leave helper. 477 */ 478 class CProfile2SpikeRegion 479 { 480 public: 481 CProfile2SpikeRegion(const char* name, double spikeLimit); 482 ~CProfile2SpikeRegion(); 427 483 private: 428 484 const char* m_Name; 485 double m_Limit; 486 double m_StartTime; 487 bool m_PushedHold; 429 488 }; 430 489 431 490 /** 491 * Scope-based enter/leave helper. 492 */ 493 class CProfile2AggregatedRegion 494 { 495 public: 496 CProfile2AggregatedRegion(const char* name, double spikeLimit); 497 ~CProfile2AggregatedRegion(); 498 private: 499 const char* m_Name; 500 double m_Limit; 501 double m_StartTime; 502 bool m_PushedHold; 503 }; 504 505 /** 432 506 * Scope-based GPU enter/leave helper. 433 507 */ 434 508 class CProfile2GPURegion … … 455 529 */ 456 530 #define PROFILE2(region) CProfile2Region profile2__(region) 457 531 532 #define PROFILE2_IFSPIKE(region, limit) CProfile2SpikeRegion profile2__(region, limit) 533 458 534 #define PROFILE2_GPU(region) CProfile2GPURegion profile2gpu__(region) 459 535 460 536 /** -
source/tools/profiler2/profiler2.html
6 6 $(refresh); 7 7 </script> 8 8 <style> 9 canvas { border: 1px #ddd solid; }9 canvas { border: 1px #ddd solid; display:block;} 10 10 #tooltip { background: #ffd; padding: 4px; font: 12px sans-serif; border: 1px #880 solid; } 11 11 #tooltip.long { -moz-column-count: 2; } 12 #choices p { margin:2px; cursor:pointer;} 13 #choices p:hover { background:#eee;} 14 #choices p.active { background:#fee;} 12 15 </style> 13 16 14 17 <button onclick="refresh()">Refresh</button> 18 <button onclick="SaveAsFile()">Save as File</button> 19 20 <button onclick="gather_stable_data()">Condense</button> 21 15 22 <br> 16 23 <div id="timelines"></div> 17 24 25 <div id="infodepth"> 26 <button onclick="output_graph_for_type('FlushDestroyedComponents')">output_graph_for_type</button> 27 <button onclick="output_time_graph_for_type('FlushDestroyedComponents')">output_time_graph_for_type</button> 28 <input type="file" id="ComparisonFile" name="files[]" /> 29 <div style="float:right; overflow-y:scroll; width:600px;height:600px" id="choices"> 30 <h3>Choices</h3> 31 </div> 32 <canvas id="typical" width="600" height="600"></canvas> 33 </div> 18 34 <div id="tooltip" style="position: absolute; visibility: hidden"></div> 19 35 20 36 Search: <input id="regionsearch" oninput="search_regions(this.value)"> -
source/tools/profiler2/profiler2.js
7 7 var ITEM_LEAVE = 3; 8 8 var ITEM_ATTRIBUTE = 4; 9 9 10 var g_used_colours = {}; 10 11 12 13 var g_data; 14 15 var g_last_stable_frame = 0; 16 var g_stable_data = {}; 17 var g_statistical_data = {}; 18 19 var g_ActiveElement; 20 11 21 function hslToRgb(h, s, l, a) 12 22 { 13 23 var r, g, b; … … 46 56 return hslToRgb(hs[id % hs.length], ss[Math.floor(id / hs.length) % ss.length], ls[Math.floor(id / (hs.length*ss.length)) % ls.length], 1); 47 57 } 48 58 49 var g_used_colours = {};50 51 52 var g_data;53 54 59 function refresh() 55 60 { 56 if ( 1)61 if (0) 57 62 refresh_live(); 58 63 else 59 refresh_jsonp(' ../../../binaries/system/profile2.jsonp');64 refresh_jsonp('C:/Users/ironweb-15/AppData/Local/0ad/logs/profile2.jsonp'); 60 65 } 61 66 62 67 function concat_events(data) … … 88 93 89 94 rebuild_canvases(); 90 95 update_display(range); 96 recompute_choices(0); 91 97 }; 92 98 93 99 script.src = url; … … 109 115 threads.forEach(function(thread) { 110 116 refresh_thread(thread, callback_data); 111 117 }); 118 setTimeout(refresh, 2000); 112 119 }, 113 120 error: function (jqXHR, textStatus, errorThrown) { 114 121 alert('Failed to connect to server ("'+textStatus+'")'); 122 refresh=refresh_jsonp 115 123 } 116 124 }); 117 125 } … … 143 151 } 144 152 }); 145 153 } 154 function gather_stable_data() 155 { 156 if (!g_stable_data.threads) 157 { 158 g_stable_data = {}; 159 g_statistical_data = {}; 160 for (let thread in g_data.threads) 161 { 162 g_stable_data[thread] = {"frames" : [], "events" : []} 163 g_statistical_data[thread] = {"evt_dur_avg" : {}, "evt_dur_total" : {}} 164 } 165 } 146 166 167 for (let thread in g_data.threads) 168 { 169 let in_data = g_data.threads[thread].processed_events; 170 if (!in_data) 171 in_data = compute_intervals(g_data.threads[thread].data.events, {}) 172 if (!in_data) 173 continue; 174 let out_data = g_stable_data[thread]; 175 let startTime = 0; 176 177 for (let i = 0; i < in_data.frames.length; ++i) 178 if (in_data.frames[i].t0 > g_last_stable_frame) 179 { 180 startTime = in_data.frames[i].t0; 181 out_data.frames = out_data.frames.concat(in_data.frames.slice(i)); 182 break; 183 } 184 g_last_stable_frame = in_data.frames[in_data.frames.length-1].t1; 185 // get all relevant events 186 for (let evt in in_data.intervals) 187 { 188 let event = in_data.intervals[evt]; 189 let time = event.t0; 190 if (time < startTime) 191 continue; 192 193 if (!out_data.events[event.id]) 194 { 195 out_data.events[event.id] = []; 196 g_statistical_data[thread].evt_dur_avg[event.id] = event.duration; 197 g_statistical_data[thread].evt_dur_total[event.id] = 0; 198 } 199 g_statistical_data[thread].evt_dur_avg[event.id] = g_statistical_data[thread].evt_dur_avg[event.id] * 0.99 + event.duration * 0.01; 200 g_statistical_data[thread].evt_dur_total[event.id] += event.duration; 201 202 if (event.duration > 5e-3) 203 { 204 out_data.events.push(event); 205 } 206 } 207 } 208 var blob = new Blob([JSON.stringify(g_statistical_data)], {type: "application/json"}); 209 var url = URL.createObjectURL(blob); 210 window.open(url); 211 var blob = new Blob([JSON.stringify(g_stable_data)], {type: "application/json"}); 212 var url = URL.createObjectURL(blob); 213 window.open(url); 214 } 215 216 function SaveAsFile() 217 { 218 $.ajax({ 219 url: 'http://127.0.0.1:8000/download', 220 success: function () { 221 }, 222 error: function (jqXHR, textStatus, errorThrown) { 223 } 224 }); 225 } 226 147 227 function rebuild_canvases() 148 228 { 149 229 g_data.canvas_frames = $('<canvas width="1600" height="128"></canvas>').get(0); 150 g_data.canvas_zoom = $('<canvas width="1600" height="1 28"></canvas>').get(0);230 g_data.canvas_zoom = $('<canvas width="1600" height="192"></canvas>').get(0); 151 231 g_data.text_output = $('<pre></pre>').get(0); 152 232 153 233 set_frames_zoom_handlers(g_data.canvas_frames); … … 160 240 }); 161 241 $('#timelines').append(g_data.canvas_zoom); 162 242 $('#timelines').append(g_data.text_output); 243 $('#timelines').append(g_data.canvas_typical); 163 244 } 164 245 165 246 function update_display(range) … … 190 271 set_zoom_handlers(processed_main, processed_data, thread.canvas, g_data.canvas_zoom); 191 272 set_tooltip_handlers(thread.canvas); 192 273 }); 193 274 } 194 275 195 276 function display_top_items(data, output) 196 277 { … … 226 307 var tmin, tmax; 227 308 228 309 var frames = []; 229 var last_frame_time = undefined; 310 var last_frame_time_start = undefined; 311 var last_frame_time_end = undefined; 230 312 for (var i = 0; i < data.length; ++i) 231 313 { 232 314 if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') 233 315 { 234 var t = data[i][1]; 235 if (last_frame_time) 236 frames.push({'t0': last_frame_time, 't1': t}); 237 last_frame_time = t; 316 if (last_frame_time_end) 317 frames.push({'t0': last_frame_time_start, 't1': last_frame_time_end}); 318 last_frame_time_start = data[i][1]; 238 319 } 320 if (data[i][0] == ITEM_LEAVE && data[i][2] == 'frame') 321 { 322 last_frame_time_end = data[i][1]; 323 } 239 324 } 240 325 241 326 if (range.numframes) … … 360 445 var stack = []; 361 446 var lastT = 0; 362 447 var lastWasEvent = false; 448 363 449 for (var i = start; i <= end; ++i) 364 450 { 365 451 if (data[i][0] == ITEM_EVENT) … … 373 459 else if (data[i][0] == ITEM_ENTER) 374 460 { 375 461 // if (data[i][1] < lastT) 376 // console.log('Time went backwards: ' + (data[i][1] - lastT));462 // console.log('Time - ENTER went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i])); 377 463 378 464 stack.push({'t0': data[i][1], 'id': data[i][2]}); 379 465 … … 383 469 else if (data[i][0] == ITEM_LEAVE) 384 470 { 385 471 // if (data[i][1] < lastT) 386 // console.log('Time went backwards: ' + (data[i][1] - lastT));472 // console.log('Time - LEAVE went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i])); 387 473 388 474 lastT = data[i][1]; 389 475 lastWasEvent = false; … … 390 476 391 477 if (!stack.length) 392 478 continue; 479 393 480 var interval = stack.pop(); 394 481 482 483 395 484 if (data[i][2] != interval.id && data[i][2] != '(ProfileStop)') 396 alert('inconsistent interval ids ('+interval.id+' / '+data[i][2]+')');485 console.log('inconsistent interval ids ('+interval.id+' / '+data[i][2]+')'); 397 486 398 487 if (!g_used_colours[interval.id]) 399 488 g_used_colours[interval.id] = new_colour(num_colours++); … … 403 492 interval.t1 = data[i][1]; 404 493 interval.duration = interval.t1 - interval.t0; 405 494 interval.depth = stack.length; 406 407 495 intervals.push(interval); 496 if (interval.id == "Script" && interval.attrs && interval.attrs.length) 497 { 498 let curT = interval.t0; 499 for (let subItem in interval.attrs) 500 { 501 let sub = interval.attrs[subItem]; 502 if (sub.search("buffer") != -1) 503 continue; 504 let newInterv = {}; 505 newInterv.t0 = curT; 506 newInterv.duration = +sub.replace(/.+? ([.0-9]+)us/, "$1")/1000000; 507 newInterv.t1 = curT + newInterv.duration; 508 curT += newInterv.duration; 509 newInterv.id = "Script:" + sub.replace(/(.+?) ([.0-9]+)us/, "$1"); 510 newInterv.colour = g_used_colours[interval.id]; 511 newInterv.depth = interval.depth+1; 512 intervals.push(newInterv); 513 } 514 } 408 515 } 409 516 else if (data[i][0] == ITEM_ATTRIBUTE) 410 517 { … … 454 561 } 455 562 }; 456 563 457 // var y_per_second = 1000; 458 var y_per_second = 100;564 // log 100 scale, skip < 15 ms (60fps) 565 var scale = x => 1 - Math.max(0, Math.log(1 + (x-15)/10) / Math.log(100)); 459 566 460 [16, 33, 200, 500].forEach(function(t) {461 var y1 = canvas.height;462 var y0 = y1 - t/1000*y_per_second;463 var y = Math.floor(y0) + 0.5;464 465 ctx.beginPath();466 ctx.moveTo(xpadding, y);467 ctx.lineTo(canvas.width - xpadding, y);468 ctx.strokeStyle = 'rgb(255, 0, 0)';469 ctx.stroke();470 ctx.fillStyle = 'rgb(255, 0, 0)';471 ctx.fillText(t+'ms', 0, y-2);472 });473 474 567 ctx.strokeStyle = 'rgb(0, 0, 0)'; 475 568 ctx.fillStyle = 'rgb(255, 255, 255)'; 476 569 for (var i = 0; i < data.frames.length; ++i) … … 481 574 var x0 = xpadding + dx*(frame.t0 - tmin); 482 575 var x1 = x0 + dx*duration; 483 576 var y1 = canvas.height; 484 var y0 = y1 - duration*y_per_second;577 var y0 = y1 * scale(duration*1000); 485 578 486 579 ctx.beginPath(); 487 580 ctx.rect(x0, y0, x1-x0, y1-y0); … … 504 597 }); 505 598 } 506 599 600 [16, 33, 200, 500].forEach(function(t) { 601 var y1 = canvas.height; 602 var y0 = y1 * scale(t); 603 var y = Math.floor(y0) + 0.5; 604 605 ctx.beginPath(); 606 ctx.moveTo(xpadding, y); 607 ctx.lineTo(canvas.width - xpadding, y); 608 ctx.strokeStyle = 'rgb(255, 0, 0)'; 609 ctx.stroke(); 610 ctx.fillStyle = 'rgb(255, 0, 0)'; 611 ctx.fillText(t+'ms', 0, y-2); 612 }); 613 507 614 ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)'; 508 615 ctx.fillStyle = 'rgba(128, 128, 255, 0.2)'; 509 616 ctx.beginPath(); … … 534 641 535 642 var x = time_to_x(event.t); 536 643 var y = 32; 644 645 if (x < 2) 646 continue; 537 647 538 648 var x0 = x; 539 649 var x1 = x; … … 611 721 var precision = -3; 612 722 while ((tmax-tmin)*Math.pow(10, 3+precision) < 25) 613 723 ++precision; 724 if (precision > 20) 725 precision = 20; 726 if (precision < 0) 727 precision = 0; 614 728 var ticks_per_sec = Math.pow(10, 3+precision); 615 729 var major_tick_interval = 5; 616 730 for (var i = 0; i < (tmax-tmin)*ticks_per_sec; ++i) … … 634 748 if (interval.tmax <= tmin || interval.tmin > tmax) 635 749 continue; 636 750 751 var x0 = Math.floor(time_to_x(interval.t0)); 752 var x1 = Math.floor(time_to_x(interval.t1)); 753 //if (x1-x0 < 1) 754 // continue; 755 var y0 = padding_top + interval.depth * BAR_SPACING; 756 var y1 = y0 + BAR_SPACING; 757 637 758 var label = interval.id; 638 759 if (interval.attrs) 639 760 { … … 642 763 else 643 764 label += ' [...]'; 644 765 } 645 var x0 = Math.floor(time_to_x(interval.t0)); 646 var x1 = Math.floor(time_to_x(interval.t1)); 647 var y0 = padding_top + interval.depth * BAR_SPACING; 648 var y1 = y0 + BAR_SPACING; 649 766 650 767 ctx.fillStyle = interval.colour; 651 768 ctx.strokeStyle = 'black'; 652 769 ctx.beginPath(); … … 723 840 var relativeY = event.pageY - this.offsetTop; 724 841 725 842 // var width = 0.001 + 0.5 * relativeY / canvas0.height; 726 var width = 0.001 + 5 * relativeY / canvas0.height; 843 var width = relativeY / canvas0.height; 844 width = width*width; 845 width *= zdata.x_to_t(canvas0.width)/10; 727 846 728 847 var tavg = zdata.x_to_t(relativeX); 729 848 var tmax = tavg + width/2; … … 759 878 } 760 879 761 880 var relativeX = event.pageX - this.offsetLeft; 762 var relativeY = event.pageY - this.offsetTop; 763 var width = 8 + 64 * relativeY / canvas0.height; 881 var relativeY = (event.pageY + this.offsetTop) / canvas0.height; 882 relativeY = relativeY - 0.5; 883 relativeY *= 10; 884 relativeY *= relativeY; 885 var width = relativeY / canvas0.height; 886 width = width*width; 887 width = 6 + width * x_to_time(canvas0.width)/10; 764 888 var zoom = { tmin: x_to_time(relativeX-width/2), tmax: x_to_time(relativeX+width/2) }; 765 889 display_hierarchy(main_data, data, canvas0, hdata.range, zoom); 766 890 display_hierarchy(main_data, data, canvas1, zoom, undefined); … … 821 945 $(canvas).mousemove(function(event) { 822 946 do_tooltip.call(this, event); 823 947 }); 948 $(canvas).mouseleave(function(event) { 949 $('#tooltip').css('visibility', 'hidden'); 950 }); 824 951 } 825 952 826 953 function search_regions(query) … … 846 973 { 847 974 out.append($('<tr><td>?</td><td>' + found[i].id + '</td><td>' + (found[i].duration*1000) + '</td></tr>')); 848 975 } 849 } 850 No newline at end of file 976 } 977 978 function output_graph_for_type(type, useLog) 979 { 980 // Search through out processed intervals for those matching type and keep them in a vector. 981 var canvas = document.getElementById("typical"); 982 var context = canvas.getContext("2d"); 983 context.clearRect(0, 0, canvas.width, canvas.height); 984 canvas._tooltips = []; 985 986 var events = []; 987 var eventsByFrame = []; 988 var useLogScale = useLog; 989 990 var max = 0; 991 var avg = 0; 992 var data = compute_intervals(g_data.threads[0].data.events, {}); 993 var currFrame = 0; 994 for (var i = 0; i < data.intervals.length; i++) 995 { 996 let interval = data.intervals[i]; 997 if (interval.id == type) 998 { 999 let frame = currFrame; 1000 eventsByFrame.push(0); 1001 while (frame < data.frames.length && data.frames[frame].t1 < interval.t0) 1002 { 1003 eventsByFrame.push(0); 1004 frame++ 1005 } 1006 eventsByFrame[frame] += interval.duration; 1007 } 1008 }; 1009 let event_count = 0; 1010 for (var i = 0; i < eventsByFrame.length; i++) 1011 { 1012 if (!eventsByFrame[i]) 1013 continue; 1014 event_count++; 1015 events.push(eventsByFrame[i]); 1016 avg += +eventsByFrame[i]; 1017 if (eventsByFrame[i] > max) 1018 max = eventsByFrame[i]; 1019 } 1020 console.log(avg); 1021 avg /= event_count; 1022 1023 if (useLogScale == undefined && max > avg * 3) 1024 useLogScale = true; 1025 events.sort(); 1026 1027 var maxXScale = Math.ceil(max*20)/20; 1028 var maxYScaleNb = 0; 1029 var maxYScaleTime = 0; 1030 1031 let p = 0; 1032 for (var i = 0; i < canvas.width; i++) 1033 { 1034 let nbForX = 0; 1035 let timeForX = 0; 1036 while (events[p] < maxXScale*(i/canvas.width)) 1037 { 1038 timeForX += events[p]; 1039 nbForX++; 1040 p++; 1041 } 1042 if (Math.ceil(nbForX/100.0)*100.0 > maxYScaleNb) 1043 maxYScaleNb = Math.ceil(nbForX/100.0)*100.0; 1044 if (Math.ceil(timeForX*10)/10 > maxYScaleTime) 1045 maxYScaleTime = Math.ceil(timeForX*10)/10; 1046 } 1047 p = 0; 1048 var maxx = max * (events.length); 1049 for (var i = 0; i < canvas.width; i++) 1050 { 1051 let nbForX = 0; 1052 let timeForX = 0; 1053 1054 1055 let i0 = i/canvas.width*maxXScale; 1056 let i1 = (i+1)/canvas.width*maxXScale; 1057 1058 if (useLogScale) 1059 { 1060 i0 = maxXScale - Math.log10(1 + (1-i/canvas.width) * 9)*maxXScale; 1061 i1 = maxXScale - Math.log10(1 + (1-(i+1)/canvas.width) * 9)*maxXScale; 1062 } 1063 1064 while (events[p] < i1) 1065 { 1066 timeForX += events[p]; 1067 nbForX++; 1068 p++; 1069 } 1070 1071 context.globalCompositeOperation = "lighter"; 1072 1073 context.beginPath(); 1074 context.fillStyle="rgba(0,0,255,1)"; 1075 context.fillRect(i,canvas.height,1,-timeForX/maxYScaleTime*canvas.height); 1076 1077 context.beginPath(); 1078 context.fillStyle="rgba(255,0,0,1)"; 1079 context.fillRect(i,canvas.height,1,-nbForX/maxYScaleNb*canvas.height); 1080 1081 canvas._tooltips.push({ 1082 'x0': i, 'x1': i+1, 1083 'y0': 0, 'y1': canvas.height, 1084 'text': function(nb,t) { return function() { return (nb + " events at " + Math.round(t*10000)/10 + "ms"); } }(nbForX,i1) 1085 }); 1086 } 1087 if (useLogScale) 1088 { 1089 for (var i = 1; i < 4; i++) 1090 { 1091 let xv = i/5; 1092 let val = maxXScale - Math.log10(1 + (1-xv) * 9)*maxXScale; 1093 context.beginPath(); 1094 context.lineWidth="1"; 1095 context.strokeStyle = "rgba(0,0,0,0.2)"; 1096 context.moveTo(xv*canvas.width,20); 1097 context.lineTo(xv*canvas.width,canvas.height); 1098 context.stroke(); 1099 context.fillStyle = "gray"; 1100 context.font = "10px Arial"; 1101 context.textAlign="center"; 1102 context.fillText(Math.round(val*1000) + "ms",xv*canvas.width,12); 1103 }; 1104 } 1105 set_tooltip_handlers(canvas); 1106 } 1107 1108 1109 function output_time_graph_for_type(type, useLog) 1110 { 1111 // Search through out processed intervals for those matching type and keep them in a vector. 1112 var canvas = document.getElementById("typical"); 1113 var context = canvas.getContext("2d"); 1114 context.clearRect(0, 0, canvas.width, canvas.height); 1115 canvas._tooltips = []; 1116 1117 TimeByFrame = []; 1118 1119 var max = 0; 1120 var avg = 0; 1121 var currFrame = 0; 1122 var data = compute_intervals(g_data.threads[0].data.events, {}); 1123 for (var i = 0; i < data.intervals.length; i++) 1124 { 1125 TimeByFrame.push(0); 1126 let interval = data.intervals[i]; 1127 if (interval.id == type) 1128 { 1129 let frame = currFrame; 1130 while (frame < data.frames.length && data.frames[frame].t1 < interval.t0) 1131 frame++ 1132 TimeByFrame[frame] += interval.duration; 1133 if (TimeByFrame[frame] > max) 1134 max = TimeByFrame[frame]; 1135 avg += interval.duration; 1136 currFrame = frame; 1137 } 1138 }; 1139 1140 for (var i = 0; i < data.frames.length; i++) 1141 { 1142 let xScale = data.frames.length/canvas.width; 1143 1144 let y = TimeByFrame[i]/max; 1145 if (max > avg*5) 1146 y = Math.log10(1 + TimeByFrame[i]/max * 9); 1147 1148 context.beginPath(); 1149 context.lineWidth="6"; 1150 context.fillStyle="rgba(0,0,255,1)"; 1151 context.fillRect(i/xScale,canvas.height,1/xScale,-y*canvas.height); 1152 1153 canvas._tooltips.push({ 1154 'x0': i/xScale, 'x1': (i+1)/xScale, 1155 'y0': 0, 'y1': canvas.height, 1156 'text': function(frame,t) { return function() { return ("Frame " + frame + ": " + t*1000 + "ms"); } }(i,TimeByFrame[i]) 1157 }); 1158 } 1159 set_tooltip_handlers(canvas); 1160 } 1161 1162 function recompute_choices(thread) 1163 { 1164 var choices = document.getElementById("choices"); 1165 var types = {}; 1166 var data = compute_intervals(g_data.threads[thread].data.events, {}); 1167 for (let i = 0; i < data.intervals.length; i++) 1168 types[data.intervals[i].id] = 0; 1169 1170 var sorted_keys = Object.keys(types).sort(); 1171 1172 for (let key in sorted_keys) 1173 { 1174 let type = sorted_keys[key]; 1175 let p = document.createElement("p"); 1176 p.textContent = type; 1177 1178 choices.appendChild(p); 1179 p.onclick = function() 1180 { 1181 choices.parentNode.querySelector("button:first-child").onclick = function() { output_graph_for_type(type); }; 1182 choices.parentNode.querySelector("button:last-of-type").onclick = function() { output_time_graph_for_type(type); }; 1183 1184 var allP = choices.querySelectorAll("p"); 1185 for (let item in allP) 1186 { 1187 allP[item] . className = ""; 1188 } 1189 p.className = "active"; 1190 g_ActiveElement = p.textContent; 1191 } 1192 } 1193 } 1194 1195 function compareTwoTypes(type, dataB) 1196 { 1197 TimeByFrameA = []; 1198 TimeByFrameB = []; 1199 1200 var maxA = 0; 1201 var maxB = 0; 1202 var avgA = []; 1203 var avgB = []; 1204 var countA = 0; 1205 var countB = 0; 1206 1207 var dataA = compute_intervals(g_data.threads[0].data.events, {}); 1208 1209 let ComputePerFrame = function(data, TimeByFrame) 1210 { 1211 let currFrame = 0; 1212 for (var i = 0; i < data.intervals.length; i++) 1213 { 1214 TimeByFrame.push(0); 1215 let interval = data.intervals[i]; 1216 if (interval.id == type) 1217 { 1218 while (currFrame < data.frames.length && data.frames[currFrame].t1 < interval.t0) 1219 currFrame++ 1220 TimeByFrame[currFrame] += interval.duration; 1221 } 1222 }; 1223 } 1224 ComputePerFrame(dataA,TimeByFrameA); 1225 ComputePerFrame(dataB,TimeByFrameB); 1226 let statA = {"avg":[],"max":0,"count":0}; 1227 let statB = {"avg":[],"max":0,"count":0}; 1228 let StatPerFrame = function(statRet, TimeByFrame) 1229 { 1230 for (var i = 0; i < TimeByFrame.length; i++) 1231 { 1232 if (!TimeByFrame[i]) 1233 continue; 1234 statRet.avg.push(TimeByFrame[i]); 1235 if (TimeByFrame[i] > statRet.max) 1236 statRet.max = TimeByFrame[i]; 1237 statRet.count++; 1238 } 1239 } 1240 StatPerFrame(statA,TimeByFrameA); 1241 StatPerFrame(statB,TimeByFrameB); 1242 1243 // pick the median 1244 statA.avg.sort(); 1245 statB.avg.sort(); 1246 statA.avg = statA.avg[Math.round(statA.avg.length/2)]; 1247 statB.avg = statB.avg[Math.round(statB.avg.length/2)]; 1248 1249 if (!statA.avg) 1250 statA.avg = 0; 1251 if (!statB.avg) 1252 statB.avg = 0; 1253 1254 let canvas = null; 1255 1256 // compare frame by frame. 1257 // draw a canvas with two lines. 1258 canvas = document.createElement("canvas"); 1259 canvas.width = 1000; 1260 canvas.height = 400; 1261 let context = canvas.getContext("2d"); 1262 1263 context.globalCompositeOperation = "lighten"; 1264 1265 let framesBetter = 0; 1266 let betterSum = 0; 1267 let avgDiffPerFrame = 0; 1268 1269 let maxAB = Math.max(statA.max,statB.max); 1270 let nbFrames = Math.min(dataA.frames.length,dataB.frames.length); 1271 let logScale = x => Math.log10(1 + x * 9); 1272 let improvLine = 0,lastImprovLine = 0; 1273 // Draw frame by frame for each; 1274 for (var i = 0; i < nbFrames - 1; i++) 1275 { 1276 improvLine += TimeByFrameA[i] - TimeByFrameB[i]; 1277 avgDiffPerFrame += TimeByFrameA[i] - TimeByFrameB[i]; 1278 if (TimeByFrameA[i] < TimeByFrameB[i]) 1279 betterSum++; 1280 else if (TimeByFrameA[i] > TimeByFrameB[i]) 1281 betterSum--; 1282 1283 if (TimeByFrameA[i] <= TimeByFrameB[i]) 1284 framesBetter++; 1285 1286 let y0 = TimeByFrameA[i]/maxAB * canvas.height; 1287 let y1 = TimeByFrameA[i+1]/maxAB * canvas.height; 1288 let x0 = i/dataB.frames.length*canvas.width; 1289 let x1 = (i+1)/dataB.frames.length*canvas.width; 1290 if (maxAB > statA.avg*5 && maxAB > statB.avg*5) 1291 { 1292 y0 = logScale(TimeByFrameA[i]/maxAB) * canvas.height; 1293 y1 = logScale(TimeByFrameA[i+1]/maxAB) * canvas.height; 1294 } 1295 context.beginPath(); 1296 context.lineWidth=0.5; 1297 context.moveTo(x0, canvas.height - y0); 1298 context.lineTo(x1, canvas.height - y1); 1299 context.strokeStyle = "red"; 1300 context.stroke(); 1301 1302 y0 = TimeByFrameB[i]/maxAB * canvas.height; 1303 y1 = TimeByFrameB[i+1]/maxAB * canvas.height; 1304 x0 = i/dataB.frames.length*canvas.width; 1305 x1 = (i+1)/dataB.frames.length*canvas.width; 1306 if (maxAB > statA.avg*5 && maxAB > statB.avg*5) 1307 { 1308 y0 = logScale(TimeByFrameB[i]/maxAB) * canvas.height; 1309 y1 = logScale(TimeByFrameB[i+1]/maxAB) * canvas.height; 1310 } 1311 1312 context.beginPath(); 1313 context.lineWidth=0.5; 1314 context.moveTo(x0, canvas.height - y0); 1315 context.lineTo(x1, canvas.height - y1); 1316 context.strokeStyle = "blue"; 1317 context.stroke(); 1318 1319 context.beginPath(); 1320 context.lineWidth=0.5; 1321 context.moveTo(x0, 200 + lastImprovLine/nbFrames*10000); 1322 context.lineTo(x1, 200 + improvLine/nbFrames*10000); 1323 context.strokeStyle = "green"; 1324 context.stroke(); 1325 1326 lastImprovLine = improvLine; 1327 } 1328 let res = document.getElementById("ComparisonResult"); 1329 if (res) 1330 res.parentNode.removeChild(res); 1331 res = document.createElement("div"); 1332 res.id="ComparisonResult"; 1333 1334 let smRound = (x, y) => Math.round(x*y*1000)/y; 1335 1336 res.innerHTML = "<h4>Comparison of " + type + "</h4>"; 1337 res.innerHTML += "<p>Max: " + smRound(statA.max,10) + "ms - " + statB.max + "ms</p>"; 1338 res.innerHTML += "<p>Average: " + statA.avg + " - " + statB.avg + "</p>"; 1339 res.innerHTML += "<p>Count: " + statA.count + " - " + statB.count + "</p>"; 1340 res.innerHTML += "<p>Average difference per frame: " + avgDiffPerFrame/nbFrames*1000 + "ms</p>" 1341 res.innerHTML += "<p>In number of frames: " + betterSum + "</p>"; 1342 res.innerHTML += "<p>% of Frames where current is better: " + Math.round(framesBetter/nbFrames*100) + "%</p>"; 1343 res.appendChild(canvas); 1344 1345 document.querySelector("body").appendChild(res); 1346 } 1347 1348 function compareWith(other) 1349 { 1350 // let's compute. If we fail at any point, warn 1351 try 1352 { 1353 let out_data_other; 1354 var script = document.createElement('script'); 1355 window.profileDataCB = function(data) 1356 { 1357 script.parentNode.removeChild(script); 1358 1359 let events = concat_events(data.threads[0].data); 1360 out_data_other = compute_intervals(events, {}); 1361 if (!out_data_other.intervals.length) 1362 { 1363 console.log("failed to parse intervals"); 1364 return; 1365 } 1366 // We have intervals. Let's compare them with the active 1367 compareTwoTypes(g_ActiveElement, out_data_other); 1368 }; 1369 1370 script.innerHTML = other; 1371 document.body.appendChild(script); 1372 1373 } catch(e) 1374 { 1375 console.log("Failed to Parse File"); 1376 } 1377 } 1378 1379 function loadComparisonFile(evt) { 1380 var file = evt.target.files[0]; 1381 if (!file) 1382 return; 1383 evt.target.value = null; 1384 var reader = new FileReader(); 1385 1386 reader.onload = (function(theFile) { 1387 return function(e) 1388 { 1389 compareWith(e.target.result); 1390 }; 1391 })(file); 1392 1393 reader.readAsText(file); 1394 } 1395 1396 window.onload = function() { document.getElementById('ComparisonFile').addEventListener('change', loadComparisonFile, false); } 1397 No newline at end of file