Ticket #4056: Profiler2.patch
File Profiler2.patch, 22.8 KB (added by , 8 years ago) |
---|
-
source/ps/Profiler2.cpp
80 80 std::stringstream stream; 81 81 82 82 std::string uri = request_info->uri; 83 if (uri == "/overview") 83 84 if (uri == "/download") 84 85 { 86 profiler->SaveToFile(); 87 } 88 else if (uri == "/overview") 89 { 85 90 profiler->ConstructJSONOverview(stream); 86 91 } 87 92 else if (uri == "/query") … … 191 196 void CProfiler2::ShutDownHTTP() 192 197 { 193 198 LOGMESSAGERENDER("Shutting down profiler2 HTTP server"); 199 194 200 if (m_MgContext) 195 201 { 196 202 mg_stop(m_MgContext); … … 219 225 220 226 ENSURE(!m_GPU); // must shutdown GPU before profiler 221 227 228 SaveToFile(); 229 222 230 if (m_MgContext) 223 231 { 224 232 mg_stop(m_MgContext); … … 301 309 } 302 310 303 311 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())312 m_Profiler(profiler), m_Name(name), m_BufferPos0(0), m_BufferPos1(0), m_LastTime(timer_Time()), m_HeldDepth(0) 305 313 { 306 314 m_Buffer = new u8[BUFFER_SIZE]; 307 315 memset(m_Buffer, ITEM_NOP, BUFFER_SIZE); … … 355 363 Write(ITEM_ATTRIBUTE, buffer, 4 + len); 356 364 } 357 365 366 size_t CProfiler2::ThreadStorage::HoldLevel() 367 { 368 return m_HeldDepth; 369 } 358 370 371 void CProfiler2::ThreadStorage::PutOnHold() 372 { 373 m_HeldDepth++; 374 m_HoldBuffers[m_HeldDepth - 1].clear(); 375 } 376 377 void CProfiler2::ThreadStorage::HoldToBuffer() 378 { 379 if (m_HeldDepth > 1) 380 { 381 // copy onto buffer below 382 HoldBuffer& copied = m_HoldBuffers[m_HeldDepth - 1]; 383 HoldBuffer& target = m_HoldBuffers[m_HeldDepth - 2]; 384 if (target.pos + copied.pos > HOLD_BUFFER_SIZE) 385 return; // too much data, too bad 386 387 memcpy(&target.buffer[target.pos], copied.buffer, copied.pos); 388 389 target.pos += copied.pos; 390 } 391 else if (m_HeldDepth) 392 { 393 u32 size = m_HoldBuffers[m_HeldDepth - 1].pos; 394 u32 start = m_BufferPos0; 395 if (start + size > BUFFER_SIZE) 396 { 397 m_BufferPos0 = size; 398 COMPILER_FENCE; 399 memset(m_Buffer + start, 0, BUFFER_SIZE - start); 400 start = 0; 401 } 402 else 403 { 404 m_BufferPos0 = start + size; 405 COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer 406 } 407 memcpy(&m_Buffer[start], m_HoldBuffers[m_HeldDepth - 1].buffer, size); 408 COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer 409 m_BufferPos1 = start + size; 410 } 411 m_HeldDepth--; 412 } 413 void CProfiler2::ThreadStorage::ThrowawayHoldBuffer() 414 { 415 if (!m_HeldDepth) 416 return; 417 m_HeldDepth--; 418 } 419 359 420 void CProfiler2::ConstructJSONOverview(std::ostream& stream) 360 421 { 361 422 TIMER(L"profile2 overview"); … … 438 499 pos += sizeof(item); 439 500 if (lastTime >= 0) 440 501 { 441 lastTime = lastTime + (double)item.dt;442 visitor.OnEvent(lastTime , item.id);502 //lastTime = lastTime + (double)item.dt; 503 visitor.OnEvent(lastTime + (double)item.dt, item.id); 443 504 } 444 505 break; 445 506 } … … 450 511 pos += sizeof(item); 451 512 if (lastTime >= 0) 452 513 { 453 lastTime = lastTime + (double)item.dt;454 visitor.OnEnter(lastTime , item.id);514 //lastTime = lastTime + (double)item.dt; 515 visitor.OnEnter(lastTime + (double)item.dt, item.id); 455 516 } 456 517 break; 457 518 } … … 462 523 pos += sizeof(item); 463 524 if (lastTime >= 0) 464 525 { 465 lastTime = lastTime + (double)item.dt;466 visitor.OnLeave(lastTime , item.id);526 //lastTime = lastTime + (double)item.dt; 527 visitor.OnLeave(lastTime + (double)item.dt, item.id); 467 528 } 468 529 break; 469 530 } … … 596 657 } 597 658 stream << "\n]});\n"; 598 659 } 660 661 CProfile2SpikeRegion::CProfile2SpikeRegion(const char* name, double spikeLimit) : m_Name(name), m_Limit(spikeLimit) 662 { 663 g_Profiler2.HoldMessages(); 664 COMPILER_FENCE; 665 g_Profiler2.RecordRegionEnter(m_Name); 666 m_StartTime = g_Profiler2.GetTime(); 667 } 668 CProfile2SpikeRegion::~CProfile2SpikeRegion() 669 { 670 double time = g_Profiler2.GetTime(); 671 g_Profiler2.RecordRegionLeave(m_Name); 672 bool shouldWrite = time - m_StartTime > m_Limit; 673 674 g_Profiler2.StopHoldingMessages(shouldWrite); 675 } -
source/ps/Profiler2.h
124 124 // TODO: what's a good size? 125 125 // TODO: different threads might want different sizes 126 126 static const size_t BUFFER_SIZE = 1024*1024; 127 static const size_t HOLD_BUFFER_SIZE = 16 * 1024; 127 128 128 129 /** 129 130 * Class instantiated in every registered thread. … … 153 154 // (to save memory) without suffering from precision problems 154 155 SItem_dt_id item = { (float)(t - m_LastTime), id }; 155 156 Write(type, &item, sizeof(item)); 156 m_LastTime = t;157 //m_LastTime = t; 157 158 } 158 159 159 160 void RecordFrameStart(double t) … … 172 173 va_end(argp); 173 174 } 174 175 176 size_t HoldLevel(); 177 void PutOnHold(); 178 void HoldToBuffer(); 179 void ThrowawayHoldBuffer(); 180 175 181 CProfiler2& GetProfiler() 176 182 { 177 183 return m_Profiler; … … 195 201 */ 196 202 void Write(EItem type, const void* item, u32 itemSize) 197 203 { 204 if (m_HeldDepth > 0) 205 { 206 WriteHold(type, item, itemSize); 207 return; 208 } 198 209 // See m_BufferPos0 etc for comments on synchronisation 199 210 200 211 u32 size = 1 + itemSize; … … 224 235 m_BufferPos1 = start + size; 225 236 } 226 237 238 void WriteHold(EItem type, const void* item, u32 itemSize) 239 { 240 u32 size = 1 + itemSize; 241 if (m_HoldBuffers[m_HeldDepth-1].pos+ size > HOLD_BUFFER_SIZE) 242 return; // we held on too much data, ignore the rest 243 244 m_HoldBuffers[m_HeldDepth-1].buffer[m_HoldBuffers[m_HeldDepth-1].pos] = (u8)type; 245 memcpy(&m_HoldBuffers[m_HeldDepth-1].buffer[m_HoldBuffers[m_HeldDepth-1].pos+ 1], item, itemSize); 246 247 m_HoldBuffers[m_HeldDepth-1].pos += size; 248 } 249 227 250 CProfiler2& m_Profiler; 228 251 std::string m_Name; 229 252 230 253 double m_LastTime; // used for computing relative times 231 254 255 bool m_Hold; // are we holding messages until release/scrap?; 256 232 257 u8* m_Buffer; 233 258 259 struct HoldBuffer 260 { 261 friend class ThreadStorage; 262 public: 263 HoldBuffer() 264 { 265 buffer = new u8[HOLD_BUFFER_SIZE]; 266 memset(buffer, ITEM_NOP, HOLD_BUFFER_SIZE); 267 pos = 0; 268 } 269 ~HoldBuffer() 270 { 271 delete[] buffer; 272 } 273 void clear() 274 { 275 pos = 0; 276 } 277 u8* buffer; 278 u16 pos; 279 }; 280 281 HoldBuffer m_HoldBuffers[8]; 282 size_t m_HeldDepth; 283 234 284 // To allow hopefully-safe reading of the buffer from a separate thread, 235 285 // without any expensive synchronisation in the recording thread, 236 286 // two copies of the current buffer write position are stored. … … 327 377 GetThreadStorage().Record(ITEM_ENTER, GetTime(), id); 328 378 } 329 379 380 void RecordRegionEnter(const char* id, double time) 381 { 382 GetThreadStorage().Record(ITEM_ENTER, time, id); 383 } 384 330 385 void RecordRegionLeave(const char* id) 331 386 { 332 387 GetThreadStorage().Record(ITEM_LEAVE, GetTime(), id); … … 346 401 void RecordGPURegionLeave(const char* id); 347 402 348 403 /** 404 * Hold onto messages until a call to release or write the held messages. 405 */ 406 size_t HoldLevel() 407 { 408 return GetThreadStorage().HoldLevel(); 409 } 410 411 void HoldMessages() 412 { 413 GetThreadStorage().PutOnHold(); 414 } 415 416 void StopHoldingMessages(bool writeToBuffer) 417 { 418 if (writeToBuffer) 419 GetThreadStorage().HoldToBuffer(); 420 else 421 GetThreadStorage().ThrowawayHoldBuffer(); 422 } 423 424 /** 349 425 * Call in any thread to produce a JSON representation of the general 350 426 * state of the application. 351 427 */ … … 424 500 { 425 501 g_Profiler2.RecordRegionLeave(m_Name); 426 502 } 503 protected: 504 const char* m_Name; 505 }; 506 507 /** 508 * Scope-based enter/leave helper. 509 */ 510 class CProfile2SpikeRegion 511 { 512 public: 513 CProfile2SpikeRegion(const char* name, double spikeLimit); 514 ~CProfile2SpikeRegion(); 427 515 private: 428 516 const char* m_Name; 517 double m_Limit; 518 double m_StartTime; 429 519 }; 430 520 431 521 /** … … 455 545 */ 456 546 #define PROFILE2(region) CProfile2Region profile2__(region) 457 547 548 #define PROFILE2_IFSPIKE(region, limit) CProfile2SpikeRegion profile2__(region, limit) 549 458 550 #define PROFILE2_GPU(region) CProfile2GPURegion profile2gpu__(region) 459 551 460 552 /** -
source/tools/profiler2/profiler2.html
12 12 </style> 13 13 14 14 <button onclick="refresh()">Refresh</button> 15 <button onclick="SaveAsFile()">Save as File</button> 16 17 <button onclick="gather_stable_data()">Condense</button> 18 15 19 <br> 16 20 <div id="timelines"></div> 17 21 -
source/tools/profiler2/profiler2.js
51 51 52 52 var g_data; 53 53 54 var g_last_stable_frame = 0; 55 var g_stable_data = {}; 56 var g_statistical_data = {}; 57 54 58 function refresh() 55 59 { 56 60 if (1) 57 61 refresh_live(); 58 62 else 59 refresh_jsonp(' ../../../binaries/system/profile2.jsonp');63 refresh_jsonp('C:/Users/ironweb-15/AppData/Local/0ad/logs/profile2.jsonp'); 60 64 } 61 65 62 66 function concat_events(data) … … 109 113 threads.forEach(function(thread) { 110 114 refresh_thread(thread, callback_data); 111 115 }); 116 setTimeout(refresh, 2000); 112 117 }, 113 118 error: function (jqXHR, textStatus, errorThrown) { 114 119 alert('Failed to connect to server ("'+textStatus+'")'); 120 refresh=refresh_jsonp 115 121 } 116 122 }); 117 123 } … … 143 149 } 144 150 }); 145 151 } 152 function gather_stable_data() 153 { 154 if (!g_stable_data.threads) 155 { 156 g_stable_data = {}; 157 g_statistical_data = {}; 158 for (let thread in g_data.threads) 159 { 160 g_stable_data[thread] = {"frames" : [], "events" : []} 161 g_statistical_data[thread] = {"evt_dur_avg" : {}, "evt_dur_total" : {}} 162 } 163 } 146 164 165 for (let thread in g_data.threads) 166 { 167 let in_data = g_data.threads[thread].processed_events; 168 if (!in_data) 169 in_data = compute_intervals(g_data.threads[thread].data.events, {}) 170 if (!in_data) 171 continue; 172 let out_data = g_stable_data[thread]; 173 let startTime = 0; 174 175 for (let i = 0; i < in_data.frames.length; ++i) 176 if (in_data.frames[i].t0 > g_last_stable_frame) 177 { 178 startTime = in_data.frames[i].t0; 179 out_data.frames = out_data.frames.concat(in_data.frames.slice(i)); 180 break; 181 } 182 g_last_stable_frame = in_data.frames[in_data.frames.length-1].t1; 183 // get all relevant events 184 for (let evt in in_data.intervals) 185 { 186 let event = in_data.intervals[evt]; 187 let time = event.t0; 188 if (time < startTime) 189 continue; 190 191 if (!out_data.events[event.id]) 192 { 193 out_data.events[event.id] = []; 194 g_statistical_data[thread].evt_dur_avg[event.id] = event.duration; 195 g_statistical_data[thread].evt_dur_total[event.id] = 0; 196 } 197 g_statistical_data[thread].evt_dur_avg[event.id] = g_statistical_data[thread].evt_dur_avg[event.id] * 0.99 + event.duration * 0.01; 198 g_statistical_data[thread].evt_dur_total[event.id] += event.duration; 199 200 if (event.duration > 5e-3) 201 { 202 out_data.events.push(event); 203 } 204 } 205 } 206 var blob = new Blob([JSON.stringify(g_statistical_data)], {type: "application/json"}); 207 var url = URL.createObjectURL(blob); 208 window.open(url); 209 var blob = new Blob([JSON.stringify(g_stable_data)], {type: "application/json"}); 210 var url = URL.createObjectURL(blob); 211 window.open(url); 212 } 213 214 function SaveAsFile() 215 { 216 $.ajax({ 217 url: 'http://127.0.0.1:8000/download', 218 success: function () { 219 }, 220 error: function (jqXHR, textStatus, errorThrown) { 221 } 222 }); 223 } 224 147 225 function rebuild_canvases() 148 226 { 149 227 g_data.canvas_frames = $('<canvas width="1600" height="128"></canvas>').get(0); 150 g_data.canvas_zoom = $('<canvas width="1600" height="128"></canvas>').get(0); 228 g_data.canvas_zoom = $('<canvas width="1600" height="192"></canvas>').get(0); 229 g_data.canvas_typical = $('<canvas width="600" height="600"></canvas>').get(0); 151 230 g_data.text_output = $('<pre></pre>').get(0); 152 231 153 232 set_frames_zoom_handlers(g_data.canvas_frames); … … 160 239 }); 161 240 $('#timelines').append(g_data.canvas_zoom); 162 241 $('#timelines').append(g_data.text_output); 242 $('#timelines').append(g_data.canvas_typical); 163 243 } 164 244 165 245 function update_display(range) … … 190 270 set_zoom_handlers(processed_main, processed_data, thread.canvas, g_data.canvas_zoom); 191 271 set_tooltip_handlers(thread.canvas); 192 272 }); 273 274 var processed_main = compute_intervals(main_events, {"numframes":50}); 275 display_typical_frame(processed_main, g_data.canvas_typical); 193 276 } 194 277 195 278 function display_top_items(data, output) … … 226 309 var tmin, tmax; 227 310 228 311 var frames = []; 229 var last_frame_time = undefined; 312 var last_frame_time_start = undefined; 313 var last_frame_time_end = undefined; 230 314 for (var i = 0; i < data.length; ++i) 231 315 { 232 316 if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') 233 317 { 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; 318 if (last_frame_time_end) 319 frames.push({'t0': last_frame_time_start, 't1': last_frame_time_end}); 320 last_frame_time_start = data[i][1]; 238 321 } 322 if (data[i][0] == ITEM_LEAVE && data[i][2] == 'frame') 323 { 324 last_frame_time_end = data[i][1]; 325 } 239 326 } 240 327 241 328 if (range.numframes) … … 360 447 var stack = []; 361 448 var lastT = 0; 362 449 var lastWasEvent = false; 450 363 451 for (var i = start; i <= end; ++i) 364 452 { 365 453 if (data[i][0] == ITEM_EVENT) … … 372 460 } 373 461 else if (data[i][0] == ITEM_ENTER) 374 462 { 375 //if (data[i][1] < lastT)376 //console.log('Time went backwards: ' + (data[i][1] - lastT));463 if (data[i][1] < lastT) 464 console.log('Time went backwards: ' + (data[i][1] - lastT)); 377 465 378 466 stack.push({'t0': data[i][1], 'id': data[i][2]}); 379 467 … … 382 470 } 383 471 else if (data[i][0] == ITEM_LEAVE) 384 472 { 385 //if (data[i][1] < lastT)386 //console.log('Time went backwards: ' + (data[i][1] - lastT));473 if (data[i][1] < lastT) 474 console.log('Time went backwards: ' + (data[i][1] - lastT)); 387 475 388 476 lastT = data[i][1]; 389 477 lastWasEvent = false; … … 390 478 391 479 if (!stack.length) 392 480 continue; 481 393 482 var interval = stack.pop(); 394 483 484 485 395 486 if (data[i][2] != interval.id && data[i][2] != '(ProfileStop)') 396 alert('inconsistent interval ids ('+interval.id+' / '+data[i][2]+')');487 console.log('inconsistent interval ids ('+interval.id+' / '+data[i][2]+')'); 397 488 398 489 if (!g_used_colours[interval.id]) 399 490 g_used_colours[interval.id] = new_colour(num_colours++); … … 404 495 interval.duration = interval.t1 - interval.t0; 405 496 interval.depth = stack.length; 406 497 498 if (data[i][2] == "Post Message" && interval.duration < 0.001) 499 { 500 console.log("wrongtime at " + JSON.stringify(data[i])); 501 console.log("Stack level " + stack.length); 502 } 407 503 intervals.push(interval); 408 504 } 409 505 else if (data[i][0] == ITEM_ATTRIBUTE) … … 454 550 } 455 551 }; 456 552 457 // var y_per_second = 1000; 458 var y_per_second = 100;553 // log 100 scale, skip < 15 ms (60fps) 554 var scale = x => 1 - Math.max(0, Math.log(1 + (x-15)/10) / Math.log(100)); 459 555 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 556 ctx.strokeStyle = 'rgb(0, 0, 0)'; 475 557 ctx.fillStyle = 'rgb(255, 255, 255)'; 476 558 for (var i = 0; i < data.frames.length; ++i) … … 481 563 var x0 = xpadding + dx*(frame.t0 - tmin); 482 564 var x1 = x0 + dx*duration; 483 565 var y1 = canvas.height; 484 var y0 = y1 - duration*y_per_second;566 var y0 = y1 * scale(duration*1000); 485 567 486 568 ctx.beginPath(); 487 569 ctx.rect(x0, y0, x1-x0, y1-y0); … … 504 586 }); 505 587 } 506 588 589 [16, 33, 200, 500].forEach(function(t) { 590 var y1 = canvas.height; 591 var y0 = y1 * scale(t); 592 var y = Math.floor(y0) + 0.5; 593 594 ctx.beginPath(); 595 ctx.moveTo(xpadding, y); 596 ctx.lineTo(canvas.width - xpadding, y); 597 ctx.strokeStyle = 'rgb(255, 0, 0)'; 598 ctx.stroke(); 599 ctx.fillStyle = 'rgb(255, 0, 0)'; 600 ctx.fillText(t+'ms', 0, y-2); 601 }); 602 507 603 ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)'; 508 604 ctx.fillStyle = 'rgba(128, 128, 255, 0.2)'; 509 605 ctx.beginPath(); … … 534 630 535 631 var x = time_to_x(event.t); 536 632 var y = 32; 633 634 if (x < 2) 635 continue; 537 636 538 637 var x0 = x; 539 638 var x1 = x; … … 611 710 var precision = -3; 612 711 while ((tmax-tmin)*Math.pow(10, 3+precision) < 25) 613 712 ++precision; 713 if (precision > 20) 714 precision = 20; 715 if (precision < 0) 716 precision = 0; 614 717 var ticks_per_sec = Math.pow(10, 3+precision); 615 718 var major_tick_interval = 5; 616 719 for (var i = 0; i < (tmax-tmin)*ticks_per_sec; ++i) … … 634 737 if (interval.tmax <= tmin || interval.tmin > tmax) 635 738 continue; 636 739 740 var x0 = Math.floor(time_to_x(interval.t0)); 741 var x1 = Math.floor(time_to_x(interval.t1)); 742 //if (x1-x0 < 1) 743 // continue; 744 var y0 = padding_top + interval.depth * BAR_SPACING; 745 var y1 = y0 + BAR_SPACING; 746 637 747 var label = interval.id; 638 748 if (interval.attrs) 639 749 { … … 642 752 else 643 753 label += ' [...]'; 644 754 } 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 755 650 756 ctx.fillStyle = interval.colour; 651 757 ctx.strokeStyle = 'black'; 652 758 ctx.beginPath(); … … 713 819 ctx.restore(); 714 820 } 715 821 822 function display_typical_frame(in_data, canvas) 823 { 824 canvas._tooltips = []; 825 826 var ctx = canvas.getContext('2d'); 827 ctx.clearRect(0, 0, canvas.width, canvas.height); 828 ctx.save(); 829 830 ctx.font = '12px sans-serif'; 831 832 var xpadding = 8; 833 var padding_top = 40; 834 var width = canvas.width - xpadding*2; 835 var height = canvas.height - padding_top - 4; 836 837 function time_to_x(t) 838 { 839 return xpadding + (t - tmin) / (tmax - tmin) * width; 840 } 841 842 function x_to_time(x) 843 { 844 return tmin + (x - xpadding) * (tmax - tmin) / width; 845 } 846 847 ctx.save(); 848 849 var totalTime = {}; 850 var data = {}; 851 var tt = 0; 852 var accrued = {}; 853 for (var i = 0; i < in_data.intervals.length; ++i) 854 { 855 var interval = in_data.intervals[i]; 856 let label = interval.id; 857 if (!totalTime[label]) 858 { 859 totalTime[label] = [0,0]; 860 data[label] = interval; 861 tt = 0; 862 accrued[interval.depth] = 0; 863 } 864 if (interval.depth === 0) 865 tt += interval.duration; 866 totalTime[label][0]++; 867 totalTime[label][1]+=interval.duration; 868 } 869 870 for (let i in totalTime) 871 { 872 var interval = totalTime[i]; 873 var x0 = xpadding + (width/5) * data[i].depth; 874 var x1 = x0 + (width/5); 875 var y0 = padding_top + accrued[data[i].depth]/tt * height; 876 var y1 = y0 + interval[1]/tt * height; 877 accrued[data[i].depth] += interval[1]; 878 879 if (interval[1]/tt[data[i].depth] * height < 1) 880 continue; 881 882 var label = i; 883 884 ctx.fillStyle = data[i].colour; 885 ctx.strokeStyle = 'black'; 886 ctx.beginPath(); 887 ctx.rect(x0-0.5, y0-0.5, x1-x0, y1-y0); 888 ctx.fill(); 889 ctx.stroke(); 890 ctx.fillStyle = 'black'; 891 ctx.fillText(label, x0+2, y0+16-4, Math.max(1, x1-x0-4)); 892 893 canvas._tooltips.push({ 894 'x0': x0, 'x1': x1, 895 'y0': y0, 'y1': y1, 896 'text': function(i) { return function() { 897 var t = '<b>' + data[i].id + '</b><br>'; 898 t += 'Length: ' + time_label(data[i].duration) + '<br>'; 899 if (data[i].attrs) 900 { 901 data[i].attrs.forEach(function(attr) { 902 t += attr + '<br>'; 903 }); 904 } 905 return t; 906 }} (i) 907 }); 908 909 } 910 ctx.restore(); 911 } 912 716 913 function set_frames_zoom_handlers(canvas0) 717 914 { 718 915 function do_zoom(event) … … 723 920 var relativeY = event.pageY - this.offsetTop; 724 921 725 922 // var width = 0.001 + 0.5 * relativeY / canvas0.height; 726 var width = 0.001 + 5 * relativeY / canvas0.height; 923 var width = relativeY / canvas0.height; 924 width = width*width; 925 width *= zdata.x_to_t(canvas0.width)/10; 727 926 728 927 var tavg = zdata.x_to_t(relativeX); 729 928 var tmax = tavg + width/2; … … 759 958 } 760 959 761 960 var relativeX = event.pageX - this.offsetLeft; 762 var relativeY = event.pageY - this.offsetTop; 763 var width = 8 + 64 * relativeY / canvas0.height; 961 var relativeY = (event.pageY + this.offsetTop) / canvas0.height; 962 relativeY = relativeY - 0.5; 963 relativeY *= 10; 964 relativeY *= relativeY; 965 var width = relativeY / canvas0.height; 966 width = width*width; 967 width = 6 + width * x_to_time(canvas0.width)/10; 764 968 var zoom = { tmin: x_to_time(relativeX-width/2), tmax: x_to_time(relativeX+width/2) }; 765 969 display_hierarchy(main_data, data, canvas0, hdata.range, zoom); 766 970 display_hierarchy(main_data, data, canvas1, zoom, undefined);