1const { TouchBarScrubber } = require("electron"); 2 3// Default range: 0 to 300s, shared between both views 4var RANGE_LEFT_INIT = 0; 5var RANGE_RIGHT_INIT = 300; 6 7// Global timeline start 8var g_StartingSec = undefined; 9 10function ShouldShowDebugInfo() { 11 if (g_cb_debug_info.checked) return true; 12 else return false; 13} 14 15function GetHistoryHistogram() { 16 return HistoryHistogram; 17} 18 19function RenderHistogramForImageData(ctx, key) { 20 let PAD = 1, // To make up for the extra stroke width 21 PAD2 = 2; // To preserve some space at both ends of the histogram 22 23 let cumDensity0 = 0, cumDensity1 = 0; 24 25 // Left normalized index Left value Right normalized index, Right value 26 let threshEntry = [[undefined, undefined], [undefined, undefined]]; 27 const x = 0, y = 0, w = HISTOGRAM_W, h = HISTOGRAM_H; 28 let hist = GetHistoryHistogram()[key]; 29 if (hist == undefined) return; 30 31 let buckets = hist[2]; 32 let dw = w * 1.0 / buckets.length; 33 let maxCount = 0, totalCount = 0; 34 for (let i = 0; i < buckets.length; i++) { 35 if (maxCount < buckets[i]) { 36 maxCount = buckets[i]; 37 } 38 totalCount += buckets[i]; 39 } 40 ctx.fillStyle = '#FFF'; 41 ctx.fillRect(x, y, w, h); 42 43 ctx.strokeStyle = '#AAA'; 44 ctx.fillStyle = '#000'; 45 ctx.lineWidth = 1; 46 ctx.strokeRect(x + PAD, y + PAD, w - 2 * PAD, h - 2 * PAD); 47 for (let i = 0; i < buckets.length; i++) { 48 const bucketsLen = buckets.length; 49 if (buckets[i] > 0) { 50 let dx0 = x + PAD2 + (w - 2 * PAD2) * 1.0 * i / buckets.length, 51 dx1 = x + PAD2 + (w - 2 * PAD2) * 1.0 * (i + 1) / buckets.length, 52 dy0 = y + h - h * 1.0 * buckets[i] / maxCount, dy1 = y + h; 53 let delta_density = buckets[i] / totalCount; 54 cumDensity0 = cumDensity1; 55 cumDensity1 += delta_density; 56 57 // Write thresholds 58 if (cumDensity0 < HISTOGRAM_LEFT_TAIL_WIDTH && 59 cumDensity1 >= HISTOGRAM_LEFT_TAIL_WIDTH) { 60 threshEntry[0][0] = i / buckets.length; 61 threshEntry[0][1] = hist[0] + (hist[1] - hist[0]) / bucketsLen * i; 62 } 63 if (cumDensity0 < 1 - HISTOGRAM_RIGHT_TAIL_WIDTH && 64 cumDensity1 >= 1 - HISTOGRAM_RIGHT_TAIL_WIDTH) { 65 threshEntry[1][0] = (i - 1) / buckets.length; 66 threshEntry[1][1] = 67 hist[0] + (hist[1] - hist[0]) / bucketsLen * (i - 1); 68 } 69 70 ctx.fillRect(dx0, dy0, dx1 - dx0, dy1 - dy0); 71 } 72 } 73 74 // Mark the threshold regions 75 ctx.fillStyle = 'rgba(0,255,0,0.1)'; 76 let dx = x + PAD2; 77 dw = (w - 2 * PAD2) * 1.0 * threshEntry[0][0]; 78 ctx.fillRect(dx, y, dw, h); 79 80 ctx.fillStyle = 'rgba(255,0,0,0.1)'; 81 ctx.beginPath(); 82 dx = x + PAD2 + (w - 2 * PAD2) * 1.0 * threshEntry[1][0]; 83 dw = (w - 2 * PAD2) * 1.0 * (1 - threshEntry[1][0]); 84 ctx.fillRect(dx, y, dw, h); 85 86 IsCanvasDirty = true; 87 return [ctx.getImageData(x, y, w, h), threshEntry]; 88} 89 90function RenderHistogram(ctx, key, xMid, yMid) { 91 if (GetHistoryHistogram()[key] == undefined) { 92 return; 93 } 94 if (IpmiVizHistogramImageData[key] == undefined) { 95 return; 96 } 97 let hist = GetHistoryHistogram()[key]; 98 ctx.putImageData( 99 IpmiVizHistogramImageData[key], xMid - HISTOGRAM_W / 2, 100 yMid - HISTOGRAM_H / 2); 101 102 let ub = ''; // Upper bound label 103 ctx.textAlign = 'left'; 104 ctx.fillStyle = '#000'; 105 if (hist[1] > 1000) { 106 ub = (hist[1] / 1000.0).toFixed(1) + 'ms'; 107 } else { 108 ub = hist[1].toFixed(1) + 'us'; 109 } 110 ctx.fillText(ub, xMid + HISTOGRAM_W / 2, yMid); 111 112 let lb = ''; // Lower bound label 113 if (hist[0] > 1000) { 114 lb = (hist[0] / 1000.0).toFixed(1) + 'ms'; 115 } else { 116 lb = hist[0].toFixed(1) + 'us'; 117 } 118 ctx.textAlign = 'right'; 119 ctx.textBaseline = 'middle'; 120 ctx.fillText(lb, xMid - HISTOGRAM_W / 2, yMid); 121} 122 123// A TimelineView contains data that has already gone through 124// the Layout step and is ready for showing 125class TimelineView { 126 constructor() { 127 this.Intervals = []; 128 this.Titles = []; // { "header":true|false, "title":string, "intervals_idxes":[int] } 129 this.Heights = []; // Visual height for each line 130 this.HeaderCollapsed = {}; 131 this.TitleProperties = []; // [Visual height, Is Header] 132 this.LowerBoundTime = RANGE_LEFT_INIT; 133 this.UpperBoundTime = RANGE_RIGHT_INIT; 134 this.LowerBoundTimeTarget = this.LowerBoundTime; 135 this.UpperBoundTimeTarget = this.UpperBoundTime; 136 this.LastTimeLowerBound = 0; 137 this.LastTimeUpperBound = 0; 138 this.IsCanvasDirty = true; 139 this.IsHighlightDirty = true; 140 this.IsAnimating = false; 141 this.IpmiVizHistogramImageData = {}; 142 this.IpmiVizHistHighlighted = {}; 143 this.HighlightedRequests = []; 144 this.Canvas = undefined; 145 this.TitleDispLengthLimit = 32; // display this many chars for title 146 this.IsTimeDistributionEnabled = false; 147 this.AccentColor = '#000'; 148 this.CurrentFileName = ''; 149 this.VisualLineStartIdx = 0; 150 151 // For connecting to the data model 152 this.GroupBy = []; 153 this.GroupByStr = ''; 154 155 // For keyboard navigation 156 this.CurrDeltaX = 0; 157 this.CurrDeltaZoom = 0; 158 this.CurrShiftFlag = false; 159 this.MouseState = { 160 hovered: true, 161 pressed: false, 162 x: 0, 163 y: 0, 164 hoveredVisibleLineIndex: -999, 165 hoveredSide: undefined, // 'left', 'right', 'scroll', 'timeline' 166 drag_begin_title_start_idx: undefined, 167 drag_begin_y: undefined, 168 IsDraggingScrollBar: function() { 169 return (this.drag_begin_y != undefined); 170 }, 171 EndDragScrollBar: function() { 172 this.drag_begin_y = undefined; 173 this.drag_begin_title_start_idx = undefined; 174 }, 175 IsHoveredOverHorizontalScrollbar: function() { 176 if (this.hoveredSide == "top_horizontal_scrollbar") return true; 177 else if (this.hoveredSide == "bottom_horizontal_scrollbar") return true; 178 else return false; 179 } 180 }; 181 this.ScrollBarState = { 182 y0: undefined, 183 y1: undefined, 184 }; 185 this.HighlightedRegion = {t0: -999, t1: -999}; 186 187 // The linked view will move and zoom with this view 188 this.linked_views = []; 189 } 190 191 // Performs layout operation, move overlapping intervals to different 192 // lines 193 LayoutForOverlappingIntervals() { 194 this.Heights = []; 195 const MAX_STACK = 10; // Stack level limit: 10, arbitrarily chosen 196 197 for (let i=0; i<this.Titles.length; i++) { 198 let last_x = {}; 199 let ymax = 0; 200 201 const title_data = this.Titles[i]; 202 203 const intervals_idxes = title_data.intervals_idxes; 204 205 // TODO: What happens if there are > 1 206 if (title_data.header == false) { 207 const line = this.Intervals[intervals_idxes[0]]; 208 209 for (let j=0; j<line.length; j++) { 210 const entry = line[j]; 211 let y = 0; 212 for (; y<MAX_STACK; y++) { 213 if (!(y in last_x)) { break; } 214 if (last_x[y] <= entry[0]) { 215 break; 216 } 217 } 218 219 const end_time = entry[1]; 220 if (end_time != undefined && !isNaN(end_time)) { 221 last_x[y] = end_time; 222 } else { 223 last_x[y] = entry[0]; 224 } 225 entry[4] = y; 226 ymax = Math.max(y, ymax); 227 } 228 } else if (intervals_idxes.length == 0) { 229 // Don't do anything, set height to 1 230 } 231 this.Heights.push(ymax+1); 232 } 233 } 234 235 TotalVisualHeight() { 236 let ret = 0; 237 this.Heights.forEach((h) => { 238 ret += h; 239 }) 240 return ret; 241 } 242 243 // Returns [Index, Offset] 244 VisualLineIndexToDataLineIndex(x) { 245 if (this.Heights.length < 1) return undefined; 246 let lb = 0, ub = this.Heights[0]-1; 247 for (let i=0; i<this.Heights.length; i++) { 248 ub = lb + this.Heights[i] - 1; 249 if (lb <= x && ub >= x) { 250 return [i, x-lb]; 251 } 252 lb = ub+1; 253 } 254 return undefined; 255 } 256 257 IsEmpty() { 258 return (this.Intervals.length < 1); 259 } 260 261 GetTitleWidthLimit() { 262 if (this.IsTimeDistributionEnabled == true) { 263 return 32; 264 } else { 265 return 64; 266 } 267 } 268 269 ToLines(t, limit) { 270 let ret = []; 271 for (let i = 0; i < t.length; i += limit) { 272 let j = Math.min(i + limit, t.length); 273 ret.push(t.substr(i, j)); 274 } 275 return ret; 276 } 277 278 Zoom(dz, mid = undefined, iter = 1) { 279 if (this.CurrShiftFlag) dz *= 2; 280 if (dz != 0) { 281 if (mid == undefined) { 282 mid = (this.LowerBoundTime + this.UpperBoundTime) / 2; 283 } 284 this.LowerBoundTime = mid - (mid - this.LowerBoundTime) * (1 - dz); 285 this.UpperBoundTime = mid + (this.UpperBoundTime - mid) * (1 - dz); 286 this.IsCanvasDirty = true; 287 this.IsAnimating = false; 288 } 289 290 if (iter > 0) { 291 this.linked_views.forEach(function(v) { 292 v.Zoom(dz, mid, iter - 1); 293 }); 294 } 295 } 296 297 BeginZoomAnimation(dz, mid = undefined, iter = 1) { 298 if (mid == undefined) { 299 mid = (this.LowerBoundTime + this.UpperBoundTime) / 2; 300 } 301 this.LowerBoundTimeTarget = mid - (mid - this.LowerBoundTime) * (1 - dz); 302 this.UpperBoundTimeTarget = mid + (this.UpperBoundTime - mid) * (1 - dz); 303 this.IsCanvasDirty = true; 304 this.IsAnimating = true; 305 306 if (iter > 0) { 307 this.linked_views.forEach(function(v) { 308 v.BeginZoomAnimation(dz, mid, iter - 1); 309 }); 310 } 311 } 312 313 BeginPanScreenAnimaton(delta_screens, iter = 1) { 314 let deltat = (this.UpperBoundTime - this.LowerBoundTime) * delta_screens; 315 this.BeginSetBoundaryAnimation( 316 this.LowerBoundTime + deltat, this.UpperBoundTime + deltat); 317 318 if (iter > 0) { 319 this.linked_views.forEach(function(v) { 320 v.BeginPanScreenAnimaton(delta_screens, iter - 1); 321 }); 322 } 323 } 324 325 BeginSetBoundaryAnimation(lt, rt, iter = 1) { 326 this.IsAnimating = true; 327 this.LowerBoundTimeTarget = lt; 328 this.UpperBoundTimeTarget = rt; 329 330 if (iter > 0) { 331 this.linked_views.forEach(function(v) { 332 v.BeginSetBoundaryAnimation(lt, rt, iter - 1); 333 }); 334 } 335 } 336 337 BeginWarpToRequestAnimation(req, iter = 1) { 338 let mid_new = (req[0] + req[1]) / 2; 339 let mid = (this.LowerBoundTime + this.UpperBoundTime) / 2; 340 let lt = this.LowerBoundTime + (mid_new - mid); 341 let rt = this.UpperBoundTime + (mid_new - mid); 342 this.BeginSetBoundaryAnimation(lt, rt, 0); 343 344 this.linked_views.forEach(function(v) { 345 v.BeginSetBoundaryAnimation(lt, rt, 0); 346 }); 347 } 348 349 UpdateAnimation() { 350 const EPS = 1e-3; 351 if (Math.abs(this.LowerBoundTime - this.LowerBoundTimeTarget) < EPS && 352 Math.abs(this.UpperBoundTime - this.UpperBoundTimeTarget) < EPS) { 353 this.LowerBoundTime = this.LowerBoundTimeTarget; 354 this.UpperBoundTime = this.UpperBoundTimeTarget; 355 this.IsAnimating = false; 356 } 357 if (this.IsAnimating) { 358 let t = 0.80; 359 this.LowerBoundTime = 360 this.LowerBoundTime * t + this.LowerBoundTimeTarget * (1 - t); 361 this.UpperBoundTime = 362 this.UpperBoundTime * t + this.UpperBoundTimeTarget * (1 - t); 363 this.IsCanvasDirty = true; 364 } 365 } 366 367 IsHighlighted() { 368 return ( 369 this.HighlightedRegion.t0 != -999 && this.HighlightedRegion.t1 != -999); 370 } 371 372 RenderHistogram(ctx, key, xMid, yMid) { 373 if (GetHistoryHistogram()[key] == undefined) { 374 return; 375 } 376 if (this.IpmiVizHistogramImageData[key] == undefined) { 377 return; 378 } 379 let hist = GetHistoryHistogram()[key]; 380 ctx.putImageData( 381 this.IpmiVizHistogramImageData[key], xMid - HISTOGRAM_W / 2, 382 yMid - HISTOGRAM_H / 2); 383 384 let ub = ''; // Upper bound label 385 ctx.textAlign = 'left'; 386 ctx.fillStyle = '#000'; 387 if (hist[1] > 1000) { 388 ub = (hist[1] / 1000.0).toFixed(1) + 'ms'; 389 } else { 390 ub = hist[1].toFixed(1) + 'us'; 391 } 392 ctx.fillText(ub, xMid + HISTOGRAM_W / 2, yMid); 393 394 let lb = ''; // Lower bound label 395 if (hist[0] > 1000) { 396 lb = (hist[0] / 1000.0).toFixed(1) + 'ms'; 397 } else { 398 lb = hist[0].toFixed(1) + 'us'; 399 } 400 ctx.textAlign = 'right'; 401 ctx.textBaseline = 'middle'; 402 ctx.fillText(lb, xMid - HISTOGRAM_W / 2, yMid); 403 } 404 405 IsMouseOverTimeline() { 406 return this.MouseState.x > LEFT_MARGIN; 407 } 408 409 MouseXToTimestamp(x) { 410 let ret = (x - LEFT_MARGIN) / (RIGHT_MARGIN - LEFT_MARGIN) * 411 (this.UpperBoundTime - this.LowerBoundTime) + 412 this.LowerBoundTime; 413 ret = Math.max(ret, this.LowerBoundTime); 414 ret = Math.min(ret, this.UpperBoundTime); 415 return ret; 416 } 417 418 Unhighlight() { 419 this.HighlightedRegion.t0 = -999; 420 this.HighlightedRegion.t1 = -999; 421 } 422 423 OnMouseMove() { 424 // Drag gestures 425 if (this.MouseState.pressed == true) { 426 const h = this.MouseState.hoveredSide; 427 if (h == 'timeline') { 428 // Update highlighted area 429 this.HighlightedRegion.t1 = 430 this.MouseXToTimestamp(this.MouseState.x); 431 } 432 } 433 434 const PAD = 2; 435 if (this.MouseState.x < LEFT_MARGIN) 436 this.MouseState.hovered = false; 437 else if (this.MouseState.x > RIGHT_MARGIN) 438 this.MouseState.hovered = false; 439 else 440 this.MouseState.hovered = true; 441 442 this.IsCanvasDirty = true; 443 let lineIndex = 444 Math.floor((this.MouseState.y - YBEGIN + TEXT_Y0) / LINE_SPACING); 445 446 if (this.MouseState.x <= 0 || 447 this.MouseState.x >= RIGHT_MARGIN) { 448 lineIndex = undefined; 449 } 450 451 const old_hoveredSide = this.MouseState.hoveredSide; 452 453 // Left/right overflow markers or time axis drag 454 this.MouseState.hoveredVisibleLineIndex = -999; 455 if (this.MouseState.hoveredSide != "scrollbar" && 456 this.MouseState.pressed == false) { 457 if (lineIndex != undefined) { 458 this.MouseState.hoveredVisibleLineIndex = lineIndex; 459 460 let should_hide_cursor = false; // Should we hide the vertical cursor for linked views? 461 462 if (this.MouseState.x <= PAD + LINE_SPACING / 2 + LEFT_MARGIN && 463 this.MouseState.x >= PAD + LEFT_MARGIN) { 464 this.MouseState.hoveredSide = 'left'; 465 this.IsCanvasDirty = true; 466 } else if ( 467 this.MouseState.x <= RIGHT_MARGIN - PAD && 468 this.MouseState.x >= RIGHT_MARGIN - PAD - LINE_SPACING / 2) { 469 this.MouseState.hoveredSide = 'right'; 470 this.IsCanvasDirty = true; 471 } else if (this.MouseState.x >= PAD + LEFT_MARGIN && 472 this.MouseState.y <= TOP_HORIZONTAL_SCROLLBAR_HEIGHT && 473 this.MouseState.y > 0) { 474 this.MouseState.hoveredVisibleLineIndex = undefined; 475 this.MouseState.hoveredSide = 'top_horizontal_scrollbar'; 476 } else if (this.MouseState.x >= PAD + LEFT_MARGIN && 477 this.MouseState.y >= this.Canvas.height - BOTTOM_HORIZONTAL_SCROLLBAR_HEIGHT && 478 this.MouseState.y <= this.Canvas.height) { 479 this.MouseState.hoveredVisibleLineIndex = undefined; 480 this.MouseState.hoveredSide = 'bottom_horizontal_scrollbar'; 481 } else { 482 this.MouseState.hoveredSide = undefined; 483 } 484 } 485 } 486 487 // During a dragging session 488 if (this.MouseState.pressed == true) { 489 490 if (this.MouseState.hoveredSide == "top_horizontal_scrollbar" || 491 this.MouseState.hoveredSide == "bottom_horizontal_scrollbar") { 492 const sec_per_px = (this.MouseState.begin_UpperBoundTime - this.MouseState.begin_LowerBoundTime) / (RIGHT_MARGIN - LEFT_MARGIN); 493 const pan_secs = (this.MouseState.x - this.MouseState.begin_drag_x) * sec_per_px; 494 495 const new_lb = this.MouseState.begin_LowerBoundTime - pan_secs; 496 const new_ub = this.MouseState.begin_UpperBoundTime - pan_secs; 497 this.LowerBoundTime = new_lb; 498 this.UpperBoundTime = new_ub; 499 500 // Sync to all other views 501 this.linked_views.forEach((v) => { 502 v.LowerBoundTime = new_lb; v.UpperBoundTime = new_ub; 503 }) 504 } 505 506 const tvh = this.TotalVisualHeight(); 507 if (this.MouseState.hoveredSide == 'scrollbar') { 508 const diff_y = this.MouseState.y - this.MouseState.drag_begin_y; 509 const diff_title_idx = tvh * diff_y / this.Canvas.height; 510 let new_title_start_idx = this.MouseState.drag_begin_title_start_idx + parseInt(diff_title_idx); 511 if (new_title_start_idx < 0) { new_title_start_idx = 0; } 512 else if (new_title_start_idx >= tvh) { 513 new_title_start_idx = tvh - 1; 514 } 515 this.VisualLineStartIdx = new_title_start_idx; 516 } 517 } 518 } 519 520 OnMouseLeave() { 521 // When dragging the scroll bar, allow mouse to temporarily leave the element since we only 522 // care about delta Y 523 if (this.MouseState.hoveredSide == 'scrollbar') { 524 525 } else { 526 this.MouseState.hovered = false; 527 this.MouseState.hoveredSide = undefined; 528 this.IsCanvasDirty = true; 529 this.MouseState.hoveredVisibleLineIndex = undefined; 530 this.MouseState.y = undefined; 531 this.MouseState.x = undefined; 532 } 533 } 534 535 // Assume event.button is zero (left mouse button) 536 OnMouseDown(iter = 1) { 537 // If hovering over an overflowing triangle, warp to the nearest overflowed 538 // request on that line 539 if (this.MouseState.hoveredVisibleLineIndex >= 0 && 540 this.MouseState.hoveredVisibleLineIndex < this.Intervals.length && 541 this.MouseState.hoveredSide != undefined) { 542 const x = this.VisualLineIndexToDataLineIndex(this.MouseState.hoveredVisibleLineIndex); 543 if (x == undefined) return; 544 const line = this.Intervals[x[0]]; 545 if (this.MouseState.hoveredSide == 'left') { 546 for (let i = line.length - 1; i >= 0; i--) { 547 if (line[i][1] <= this.LowerBoundTime) { 548 this.BeginWarpToRequestAnimation(line[i]); 549 // TODO: pass timeline X to linked view 550 break; 551 } 552 } 553 } else if (this.MouseState.hoveredSide == 'right') { 554 for (let i = 0; i < line.length; i++) { 555 if (line[i][0] >= this.UpperBoundTime) { 556 // TODO: pass timeline X to linked view 557 this.BeginWarpToRequestAnimation(line[i]); 558 break; 559 } 560 } 561 } 562 } 563 564 let tx = this.MouseXToTimestamp(this.MouseState.x); 565 let t0 = Math.min(this.HighlightedRegion.t0, this.HighlightedRegion.t1), 566 t1 = Math.max(this.HighlightedRegion.t0, this.HighlightedRegion.t1); 567 if (this.MouseState.x > LEFT_MARGIN) { 568 569 // If clicking on the horizontal scroll bar, start panning the viewport 570 if (this.MouseState.hoveredSide == "top_horizontal_scrollbar" || 571 this.MouseState.hoveredSide == "bottom_horizontal_scrollbar") { 572 this.MouseState.pressed = true; 573 this.MouseState.begin_drag_x = this.MouseState.x; 574 this.MouseState.begin_LowerBoundTime = this.LowerBoundTime; 575 this.MouseState.begin_UpperBoundTime = this.UpperBoundTime; 576 } else if (tx >= t0 && tx <= t1) { 577 // If clicking inside highlighted area, zoom around the area 578 this.BeginSetBoundaryAnimation(t0, t1); 579 this.Unhighlight(); 580 this.IsCanvasDirty = true; 581 582 this.linked_views.forEach(function(v) { 583 v.BeginSetBoundaryAnimation(t0, t1, 0); 584 v.Unhighlight(); 585 v.IsCanvasDirty = false; 586 }); 587 } else { // If in the timeline area, start a new dragging action 588 this.MouseState.hoveredSide = 'timeline'; 589 this.MouseState.pressed = true; 590 this.HighlightedRegion.t0 = this.MouseXToTimestamp(this.MouseState.x); 591 this.HighlightedRegion.t1 = this.HighlightedRegion.t0; 592 this.IsCanvasDirty = true; 593 } 594 } else if (this.MouseState.x < SCROLL_BAR_WIDTH) { // Todo: draagging the scroll bar 595 const THRESH = 4; 596 if (this.MouseState.y >= this.ScrollBarState.y0 - THRESH && 597 this.MouseState.y <= this.ScrollBarState.y1 + THRESH) { 598 this.MouseState.pressed = true; 599 this.MouseState.drag_begin_y = this.MouseState.y; 600 this.MouseState.drag_begin_title_start_idx = this.VisualLineStartIdx; 601 this.MouseState.hoveredSide = 'scrollbar'; 602 } 603 } 604 605 // Collapse or expand a "header" 606 if (this.MouseState.x < LEFT_MARGIN && 607 this.MouseState.hoveredVisibleLineIndex != undefined) { 608 const x = this.VisualLineIndexToDataLineIndex(this.VisualLineStartIdx + this.MouseState.hoveredVisibleLineIndex); 609 if (x != undefined) { 610 const tidx = x[0]; 611 if (this.Titles[tidx] != undefined && this.Titles[tidx].header == true) { 612 613 // Currently, only DBus pane supports column headers, so we can hard-code the DBus re-group function (rather than to figure out which pane we're in) 614 this.HeaderCollapsed[this.Titles[tidx].title] = !(this.HeaderCollapsed[this.Titles[tidx].title]); 615 OnGroupByConditionChanged_DBus(); 616 } 617 } 618 } 619 } 620 621 // Assume event.button == 0 (left mouse button) 622 OnMouseUp() { 623 this.MouseState.EndDragScrollBar(); 624 this.MouseState.pressed = false; 625 this.IsCanvasDirty = true; 626 this.UnhighlightIfEmpty(); 627 this.IsHighlightDirty = true; 628 this.MouseState.hoveredSide = undefined; 629 } 630 631 UnhighlightIfEmpty() { 632 if (this.HighlightedRegion.t0 == this.HighlightedRegion.t1) { 633 this.Unhighlight(); 634 this.IsCanvasDirty = true; 635 return true; 636 } else 637 return false; 638 } 639 640 OnMouseWheel(event) { 641 event.preventDefault(); 642 const v = this; 643 644 let is_mouse_on_horizontal_scrollbar = false; 645 if (this.MouseState.y > 0 && this.MouseState.y < TOP_HORIZONTAL_SCROLLBAR_HEIGHT) 646 is_mouse_on_horizontal_scrollbar = true; 647 if (this.MouseState.y > this.Canvas.height - BOTTOM_HORIZONTAL_SCROLLBAR_HEIGHT && 648 this.MouseState.y < this.Canvas.height) 649 is_mouse_on_horizontal_scrollbar = true; 650 651 if (/*v.IsMouseOverTimeline()*/ is_mouse_on_horizontal_scrollbar) { 652 let dz = 0; 653 if (event.deltaY > 0) { // Scroll down, zoom out 654 dz = -0.3; 655 } else if (event.deltaY < 0) { // Scroll up, zoom in 656 dz = 0.3; 657 } 658 v.Zoom(dz, v.MouseXToTimestamp(v.MouseState.x)); 659 } else { 660 if (event.deltaY > 0) { 661 v.ScrollY(1); 662 } else if (event.deltaY < 0) { 663 v.ScrollY(-1); 664 } 665 } 666 } 667 668 ScrollY(delta) { 669 this.VisualLineStartIdx += delta; 670 if (this.VisualLineStartIdx < 0) { 671 this.VisualLineStartIdx = 0; 672 } else if (this.VisualLineStartIdx >= this.TotalVisualHeight()) { 673 this.VisualLineStartIdx = this.TotalVisualHeight() - 1; 674 } 675 } 676 677 // This function is called in Render to draw a line of Intervals. 678 // It is made into its own function for brevity in Render(). 679 // It depends on too much context so it doesn't look very clean though 680 do_RenderIntervals(ctx, intervals_j, j, dy0, dy1, 681 data_line_idx, visual_line_offset_within_data_line, 682 isAggregateSelection, 683 vars, 684 is_in_viewport) { 685 // To reduce the number of draw calls while preserve the accuracy in 686 // the visual presentation, combine rectangles that are within 1 pixel 687 // into one 688 let last_dx_begin = LEFT_MARGIN; 689 let last_dx_end = LEFT_MARGIN; 690 691 for (let i = 0; i < intervals_j.length; i++) { 692 let lb = intervals_j[i][0], ub = intervals_j[i][1]; 693 const yoffset = intervals_j[i][4]; 694 if (yoffset != visual_line_offset_within_data_line) 695 continue; 696 if (lb > ub) 697 continue; // Unmatched (only enter & no exit timestamp) 698 699 let isHighlighted = false; 700 let durationUsec = 701 (intervals_j[i][1] - intervals_j[i][0]) * 1000000; 702 let lbub = [lb, ub]; 703 if (this.IsHighlighted()) { 704 if (IsIntersected(lbub, vars.highlightedInterval)) { 705 vars.numIntersected++; 706 isHighlighted = true; 707 vars.currHighlightedReqs.push(intervals_j[i][2]); 708 } 709 } 710 711 if (ub < this.LowerBoundTime) { 712 vars.numOverflowEntriesToTheLeft++; 713 continue; 714 } 715 if (lb > this.UpperBoundTime) { 716 vars.numOverflowEntriesToTheRight++; 717 continue; 718 } 719 // Failed request 720 if (ub == undefined && lb < this.UpperBoundTime) { 721 vars.numOverflowEntriesToTheLeft++; 722 continue; 723 } 724 725 let dx0 = MapXCoord( 726 lb, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 727 this.UpperBoundTime), 728 dx1 = MapXCoord( 729 ub, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 730 this.UpperBoundTime); 731 732 dx0 = Math.max(dx0, LEFT_MARGIN); 733 dx1 = Math.min(dx1, RIGHT_MARGIN); 734 let dw = Math.max(0, dx1 - dx0); 735 736 if (isHighlighted && is_in_viewport) { 737 ctx.fillStyle = 'rgba(128,128,255,0.5)'; 738 ctx.fillRect(dx0, dy0, dw, dy1 - dy0); 739 } 740 741 let isCurrentReqHovered = false; 742 // Intersect with mouse using pixel coordinates 743 744 // When the mouse position is within 4 pixels distance from an entry, consider 745 // the mouse to be over that entry and show the information popup 746 const X_TOLERANCE = 4; 747 748 if (vars.theHoveredReq == undefined && 749 IsIntersectedPixelCoords( 750 [dx0 - X_TOLERANCE, dx0 + dw + X_TOLERANCE], 751 [this.MouseState.x, this.MouseState.x]) && 752 IsIntersectedPixelCoords( 753 [dy0, dy1], [this.MouseState.y, this.MouseState.y])) { 754 ctx.fillStyle = 'rgba(255,255,0,0.5)'; 755 if (is_in_viewport) ctx.fillRect(dx0, dy0, dw, dy1 - dy0); 756 vars.theHoveredReq = intervals_j[i][2]; 757 vars.theHoveredInterval = intervals_j[i]; 758 isCurrentReqHovered = true; 759 } 760 761 ctx.lineWidth = 0.5; 762 763 764 // If this request is taking too long/is quick enough, use red/green 765 let entry = HistogramThresholds[this.Titles[data_line_idx].title]; 766 767 let isError = false; 768 if (intervals_j[i][3] == 'error') { 769 isError = true; 770 } 771 772 if (entry != undefined) { 773 if (entry[0][1] != undefined && durationUsec < entry[0][1]) { 774 ctx.strokeStyle = '#0F0'; 775 } else if ( 776 entry[1][1] != undefined && durationUsec > entry[1][1]) { 777 ctx.strokeStyle = '#A00'; 778 } else { 779 ctx.strokeStyle = '#000'; 780 } 781 } else { 782 ctx.strokeStyle = '#000'; 783 } 784 785 const duration = intervals_j[i][1] - intervals_j[i][0]; 786 if (!isNaN(duration)) { 787 if (is_in_viewport) { 788 if (isError) { 789 ctx.fillStyle = 'rgba(192, 128, 128, 0.8)'; 790 ctx.fillRect(dx0, dy0, dw, dy1 - dy0); 791 ctx.strokeStyle = 'rgba(192, 128, 128, 1)'; 792 } else { 793 ctx.fillStyle = undefined; 794 ctx.strokeStyle = '#000'; 795 } 796 } 797 798 // This keeps track of the current "cluster" of requests 799 // that might visually overlap (i.e less than 1 pixel wide). 800 // This can greatly reduce overdraw and keep render time under 801 // a reasonable bound. 802 if (!ShouldShowDebugInfo()) { 803 if (dx0+dw - last_dx_begin > 1 || 804 i == intervals_j.length - 1) { 805 if (is_in_viewport) { 806 ctx.strokeRect(last_dx_begin, dy0, 807 /*dx0+dw-last_dx_begin*/ 808 last_dx_end - last_dx_begin, // At least 1 pixel wide 809 dy1-dy0); 810 } 811 last_dx_begin = dx0; 812 } 813 } else { 814 if (is_in_viewport) { 815 ctx.strokeRect(dx0, dy0, dw, dy1 - dy0); 816 } 817 } 818 last_dx_end = dx0 + dw; 819 this.numVisibleRequests++; 820 } else { 821 // This entry has only a beginning and not an end 822 // perhaps the original method call did not return 823 if (is_in_viewport) { 824 if (isCurrentReqHovered) { 825 ctx.fillStyle = 'rgba(192, 192, 0, 0.8)'; 826 } else { 827 ctx.fillStyle = 'rgba(255, 128, 128, 0.8)'; 828 } 829 ctx.beginPath(); 830 ctx.arc(dx0, (dy0 + dy1) / 2, HISTOGRAM_H * 0.17, 0, 2 * Math.PI); 831 ctx.fill(); 832 } 833 } 834 835 836 // Affects whether this req will be reflected in the aggregate info 837 // section 838 if ((isAggregateSelection == false) || 839 (isAggregateSelection == true && isHighlighted == true)) { 840 if (!isNaN(duration)) { 841 vars.numVisibleRequestsCurrLine++; 842 vars.totalSecsCurrLine += duration; 843 } else { 844 vars.numFailedRequestsCurrLine++; 845 } 846 847 // If a histogram exists for Titles[j], process the highlighted 848 // histogram buckets 849 if (GetHistoryHistogram()[this.Titles[data_line_idx].title] != undefined) { 850 let histogramEntry = GetHistoryHistogram()[this.Titles[data_line_idx].title]; 851 let bucketInterval = (histogramEntry[1] - histogramEntry[0]) / 852 histogramEntry[2].length; 853 let bucketIndex = 854 Math.floor( 855 (durationUsec - histogramEntry[0]) / bucketInterval) / 856 histogramEntry[2].length; 857 858 if (this.IpmiVizHistHighlighted[this.Titles[data_line_idx].title] == undefined) { 859 this.IpmiVizHistHighlighted[this.Titles[data_line_idx].title] = new Set(); 860 } 861 let entry = this.IpmiVizHistHighlighted[this.Titles[data_line_idx].title]; 862 entry.add(bucketIndex); 863 } 864 } 865 } // end for (i=0 to interval_j.length-1) 866 867 if (!ShouldShowDebugInfo()) { 868 ctx.strokeRect(last_dx_begin, dy0, 869 /*dx0+dw-last_dx_begin*/ 870 last_dx_end - last_dx_begin, // At least 1 pixel wide 871 dy1-dy0); 872 } 873 } 874 875 // For the header: 876 do_RenderHeader(ctx, header, j, dy0, dy1, 877 data_line_idx, visual_line_offset_within_data_line, 878 isAggregateSelection, 879 vars, is_in_viewport) { 880 881 const dy = (dy0+dy1) / 2; 882 ctx.fillStyle = "rgba(192,192,255, 1)"; 883 884 ctx.strokeStyle = "rgba(192,192,255, 1)" 885 886 const title_text = header.title + " (" + header.intervals_idxes.length + ")"; 887 let skip_render = false; 888 889 ctx.save(); 890 891 if (this.HeaderCollapsed[header.title] == false) { // Expanded 892 const x0 = LEFT_MARGIN - LINE_HEIGHT; 893 if (is_in_viewport) { 894 ctx.fillRect(0, dy-LINE_HEIGHT/2, x0, LINE_HEIGHT); 895 896 ctx.beginPath(); 897 ctx.moveTo(x0, dy0); 898 ctx.lineTo(x0, dy1); 899 ctx.lineTo(x0 + LINE_HEIGHT, dy1); 900 ctx.fill(); 901 ctx.closePath(); 902 903 ctx.beginPath(); 904 ctx.lineWidth = 1.5; 905 ctx.moveTo(0, dy1); 906 ctx.lineTo(RIGHT_MARGIN, dy1); 907 ctx.stroke(); 908 ctx.closePath(); 909 910 ctx.fillStyle = '#003'; 911 ctx.textBaseline = 'center'; 912 ctx.textAlign = 'right'; 913 ctx.fillText(title_text, LEFT_MARGIN - LINE_HEIGHT, dy); 914 } 915 916 // Don't draw the timelines so visual clutter is reduced 917 skip_render = true; 918 } else { 919 const x0 = LEFT_MARGIN - LINE_HEIGHT / 2; 920 if (is_in_viewport) { 921 ctx.fillRect(0, dy-LINE_HEIGHT/2, x0, LINE_HEIGHT); 922 923 ctx.beginPath(); 924 ctx.lineWidth = 1.5; 925 ctx.moveTo(x0, dy0); 926 ctx.lineTo(x0 + LINE_HEIGHT/2, dy); 927 ctx.lineTo(x0, dy1); 928 ctx.closePath(); 929 ctx.fill(); 930 931 /* 932 ctx.beginPath(); 933 ctx.moveTo(0, dy); 934 ctx.lineTo(RIGHT_MARGIN, dy); 935 ctx.stroke(); 936 ctx.closePath(); 937 */ 938 939 ctx.fillStyle = '#003'; 940 ctx.textBaseline = 'center'; 941 ctx.textAlign = 'right'; 942 ctx.fillText(title_text, LEFT_MARGIN - LINE_HEIGHT, dy); 943 } 944 } 945 946 ctx.fillStyle = "rgba(160,120,255,0.8)"; 947 948 ctx.restore(); 949 950 // Draw the merged intervals 951 // Similar to drawing the actual messages in do_Render(), but no collision detection against the mouse, and no hovering tooltip processing involved 952 const merged_intervals = header.merged_intervals; 953 let dxx0 = undefined, dxx1 = undefined; 954 for (let i=0; i<merged_intervals.length; i++) { 955 const lb = merged_intervals[i][0], ub = merged_intervals[i][1], weight = merged_intervals[i][2]; 956 let duration = ub-lb; 957 let duration_usec = duration * 1000000; 958 const lbub = [lb, ub]; 959 960 let isHighlighted = false; 961 if (this.IsHighlighted()) { 962 if (IsIntersected(lbub, vars.highlightedInterval)) { 963 vars.numIntersected += weight; 964 isHighlighted = true; 965 } 966 } 967 968 if (ub < this.LowerBoundTime) continue; 969 if (lb > this.UpperBoundTime) continue; 970 971 // Render only if collapsed 972 if (!skip_render) { 973 let dx0 = MapXCoord( 974 lb, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 975 this.UpperBoundTime), 976 dx1 = MapXCoord( 977 ub, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 978 this.UpperBoundTime); 979 dx0 = Math.max(dx0, LEFT_MARGIN); 980 dx1 = Math.min(dx1, RIGHT_MARGIN); 981 let dw = Math.max(1, dx1 - dx0); // At least 1 pixel wide during rendering 982 983 // Draw this interval 984 //ctx.fillRect(dx0, dy0, dw, dy1-dy0); 985 if (dxx0 == undefined || dxx1 == undefined) { 986 dxx0 = dx0; 987 } 988 989 const MERGE_THRESH = 0.5; // Pixels 990 991 let should_draw = true; 992 if (dxx1 == undefined || dx0 < dxx1 + MERGE_THRESH) should_draw = false; 993 if (i == merged_intervals.length - 1) { 994 should_draw = true; 995 dxx1 = dx1 + MERGE_THRESH; 996 } 997 998 if (should_draw) { 999 //console.log(dxx0 + ", " + dy0 + ", " + (dx1-dxx0) + ", " + LINE_HEIGHT); 1000 if (is_in_viewport) { 1001 ctx.fillRect(dxx0, dy0, dxx1-dxx0, LINE_HEIGHT); 1002 } 1003 dxx0 = undefined; dxx1 = undefined; 1004 } else { 1005 // merge 1006 dxx1 = dx1 + MERGE_THRESH; 1007 } 1008 } 1009 1010 if ((isAggregateSelection == false) || 1011 (isAggregateSelection == true && isHighlighted == true)) { 1012 vars.totalSecsCurrLine += duration; 1013 vars.numVisibleRequestsCurrLine += weight; 1014 } 1015 } 1016 } 1017 1018 Render(ctx) { 1019 // Wait for initialization 1020 if (this.Canvas == undefined) return; 1021 1022 // Update 1023 let toFixedPrecision = 2; 1024 const extent = this.UpperBoundTime - this.LowerBoundTime; 1025 { 1026 if (extent < 0.1) { 1027 toFixedPrecision = 4; 1028 } else if (extent < 1) { 1029 toFixedPrecision = 3; 1030 } 1031 } 1032 1033 let dx = this.CurrDeltaX; 1034 if (dx != 0) { 1035 if (this.CurrShiftFlag) dx *= 5; 1036 this.LowerBoundTime += dx * extent; 1037 this.UpperBoundTime += dx * extent; 1038 this.IsCanvasDirty = true; 1039 } 1040 1041 // Hovered interval for display 1042 let theHoveredReq = undefined; 1043 let theHoveredInterval = undefined; 1044 let currHighlightedReqs = []; 1045 1046 let dz = this.CurrDeltaZoom; 1047 this.Zoom(dz); 1048 this.UpdateAnimation(); 1049 1050 this.LastTimeLowerBound = this.LowerBoundTime; 1051 this.LastTimeUpperBound = this.UpperBoundTime; 1052 1053 if (this.IsCanvasDirty) { 1054 this.IsCanvasDirty = false; 1055 // Shorthand for HighlightedRegion.t{0,1} 1056 let t0 = undefined, t1 = undefined; 1057 1058 // Highlight 1059 let highlightedInterval = []; 1060 let numIntersected = 1061 0; // How many requests intersect with highlighted area 1062 if (this.IsHighlighted()) { 1063 t0 = Math.min(this.HighlightedRegion.t0, this.HighlightedRegion.t1); 1064 t1 = Math.max(this.HighlightedRegion.t0, this.HighlightedRegion.t1); 1065 highlightedInterval = [t0, t1]; 1066 } 1067 this.IpmiVizHistHighlighted = {}; 1068 1069 const width = this.Canvas.width; 1070 const height = this.Canvas.height; 1071 1072 ctx.globalCompositeOperation = 'source-over'; 1073 ctx.clearRect(0, 0, width, height); 1074 ctx.strokeStyle = '#000'; 1075 ctx.fillStyle = '#000'; 1076 ctx.lineWidth = 1; 1077 1078 ctx.font = '12px Monospace'; 1079 1080 // Highlight current line 1081 if (this.MouseState.hoveredVisibleLineIndex != undefined) { 1082 const hv_lidx = this.MouseState.hoveredVisibleLineIndex + this.VisualLineStartIdx; 1083 if (hv_lidx >= 0 && 1084 hv_lidx < this.Titles.length) { 1085 ctx.fillStyle = 'rgba(32,32,32,0.2)'; 1086 let dy = YBEGIN + LINE_SPACING * this.MouseState.hoveredVisibleLineIndex - 1087 LINE_SPACING / 2; 1088 ctx.fillRect(0, dy, RIGHT_MARGIN, LINE_SPACING); 1089 } 1090 } 1091 1092 // Draw highlighted background over time labels when the mouse is hovering over 1093 // the time axis 1094 ctx.fillStyle = "#FF9"; 1095 if (this.MouseState.hoveredSide == "top_horizontal_scrollbar") { 1096 ctx.fillRect(LEFT_MARGIN, 0, RIGHT_MARGIN-LEFT_MARGIN, TOP_HORIZONTAL_SCROLLBAR_HEIGHT); 1097 } else if (this.MouseState.hoveredSide == "bottom_horizontal_scrollbar") { 1098 ctx.fillRect(LEFT_MARGIN, height-BOTTOM_HORIZONTAL_SCROLLBAR_HEIGHT, RIGHT_MARGIN-LEFT_MARGIN, BOTTOM_HORIZONTAL_SCROLLBAR_HEIGHT); 1099 } 1100 1101 ctx.fillStyle = '#000'; 1102 // Time marks at the beginning & end of the visible range 1103 ctx.textBaseline = 'bottom'; 1104 ctx.textAlign = 'left'; 1105 ctx.fillText( 1106 '' + this.LowerBoundTime.toFixed(toFixedPrecision) + 's', 1107 LEFT_MARGIN + 3, height); 1108 ctx.textAlign = 'end'; 1109 ctx.fillText( 1110 '' + this.UpperBoundTime.toFixed(toFixedPrecision) + 's', 1111 RIGHT_MARGIN - 3, height); 1112 1113 ctx.textBaseline = 'top'; 1114 ctx.textAlign = 'left'; 1115 ctx.fillText( 1116 '' + this.LowerBoundTime.toFixed(toFixedPrecision) + 's', 1117 LEFT_MARGIN + 3, TEXT_Y0); 1118 ctx.textAlign = 'right'; 1119 ctx.fillText( 1120 '' + this.UpperBoundTime.toFixed(toFixedPrecision) + 's', 1121 RIGHT_MARGIN - 3, TEXT_Y0); 1122 1123 let y = YBEGIN; 1124 let numVisibleRequests = 0; 1125 1126 ctx.beginPath(); 1127 ctx.moveTo(LEFT_MARGIN, 0); 1128 ctx.lineTo(LEFT_MARGIN, height); 1129 ctx.stroke(); 1130 1131 ctx.beginPath(); 1132 ctx.moveTo(RIGHT_MARGIN, 0); 1133 ctx.lineTo(RIGHT_MARGIN, height); 1134 ctx.stroke(); 1135 1136 // Column Titles 1137 ctx.fillStyle = '#000'; 1138 let columnTitle = '(All requests)'; 1139 if (this.GroupByStr.length > 0) { 1140 columnTitle = this.GroupByStr; 1141 } 1142 ctx.textAlign = 'right'; 1143 ctx.textBaseline = 'top'; 1144 // Split into lines 1145 { 1146 let lines = this.ToLines(columnTitle, this.TitleDispLengthLimit) 1147 for (let i = 0; i < lines.length; i++) { 1148 ctx.fillText(lines[i], LEFT_MARGIN - 3, 3 + i * LINE_HEIGHT); 1149 } 1150 } 1151 1152 if (this.IsTimeDistributionEnabled) { 1153 // "Histogram" title 1154 ctx.fillStyle = '#000'; 1155 ctx.textBaseline = 'top'; 1156 ctx.textAlign = 'center'; 1157 ctx.fillText('Time Distribution', HISTOGRAM_X, TEXT_Y0); 1158 1159 ctx.textAlign = 'right' 1160 ctx.fillText('In dataset /', HISTOGRAM_X, TEXT_Y0 + LINE_SPACING - 2); 1161 1162 ctx.fillStyle = '#00F'; 1163 1164 ctx.textAlign = 'left' 1165 if (this.IsHighlighted()) { 1166 ctx.fillText( 1167 ' In selection', HISTOGRAM_X, TEXT_Y0 + LINE_SPACING - 2); 1168 } 1169 else { 1170 ctx.fillText(' In viewport', HISTOGRAM_X, TEXT_Y0 + LINE_SPACING - 2); 1171 } 1172 } 1173 1174 ctx.fillStyle = '#000'; 1175 1176 // Time Axis Breaks 1177 const breakWidths = [ 1178 86400, 10800, 3600, 1800, 1200, 600, 300, 120, 1179 60, 30, 10, 5, 2, 1, 0.5, 0.2, 1180 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.0005, 1181 0.0002, 0.0001, 0.00005, 0.00002, 0.00001 1182 ]; 1183 const BreakDrawLimit = 1000; // Only draw up to this many grid lines 1184 1185 let bidx = 0; 1186 while (bidx < breakWidths.length && 1187 breakWidths[bidx] > this.UpperBoundTime - this.LowerBoundTime) { 1188 bidx++; 1189 } 1190 let breakWidth = breakWidths[bidx + 1]; 1191 if (bidx < breakWidths.length) { 1192 let t2 = 0; // Cannot name as "t0" otherwise clash 1193 bidx = 0; 1194 while (bidx < breakWidths.length) { 1195 while (t2 + breakWidths[bidx] < this.LowerBoundTime) { 1196 t2 += breakWidths[bidx]; 1197 } 1198 if (t2 + breakWidths[bidx] >= this.LowerBoundTime && 1199 t2 + breakWidths[bidx] <= this.UpperBoundTime) { 1200 break; 1201 } 1202 bidx++; 1203 } 1204 let draw_count = 0; 1205 if (bidx < breakWidths.length) { 1206 for (; t2 < this.UpperBoundTime; t2 += breakWidth) { 1207 if (t2 > this.LowerBoundTime) { 1208 ctx.beginPath(); 1209 let dx = MapXCoord( 1210 t2, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 1211 this.UpperBoundTime); 1212 ctx.strokeStyle = '#C0C0C0'; 1213 ctx.moveTo(dx, 0); 1214 ctx.lineTo(dx, height); 1215 ctx.stroke(); 1216 ctx.closePath(); 1217 ctx.fillStyle = '#C0C0C0'; 1218 1219 ctx.textAlign = 'left'; 1220 ctx.textBaseline = 'bottom'; 1221 let label2 = t2.toFixed(toFixedPrecision) + 's'; 1222 let w = ctx.measureText(label2).width; 1223 if (dx + w > RIGHT_MARGIN) ctx.textAlign = 'right'; 1224 ctx.fillText(label2, dx, height); 1225 1226 ctx.textBaseline = 'top'; 1227 ctx.fillText(label2, dx, TEXT_Y0); 1228 1229 draw_count++; 1230 if (draw_count > BreakDrawLimit) break; 1231 } 1232 } 1233 } 1234 } 1235 1236 // Whether we aggregate selected requests or visible requests 1237 let isAggregateSelection = false; 1238 if (this.IsHighlighted()) isAggregateSelection = true; 1239 let numVisibleRequestsPerLine = {}; // DataLineIndex -> Count 1240 let numFailedRequestsPerLine = {}; 1241 let totalSecondsPerLine = {}; 1242 1243 // Range of Titles that were displayed 1244 let title_start_idx = this.VisualLineStartIdx, title_end_idx = title_start_idx; 1245 1246 const tvh = this.TotalVisualHeight(); 1247 1248 // This is used to handle Intervals that have overlapping entries 1249 let last_data_line_idx = -999;//this.VisualLineIndexToDataLineIndex(this.VisualLineStartIdx); 1250 1251 // 'j' denotes a line index; if the viewport starts from the middle of an overlapping series of 1252 // lines, 'j' will be rewinded to the first one in the series to make the counts correct. 1253 let j0 = this.VisualLineStartIdx; 1254 while (j0 > 0 && this.VisualLineIndexToDataLineIndex(j0)[1] > 0) { j0--; } 1255 1256 // If should_render is false, we're counting the entries outisde the viewport 1257 // If should_render is true, do the rendering 1258 let should_render = false; 1259 1260 // 'j' then iterates over the "visual rows" that need to be displayed. 1261 // A "visual row" might be one of: 1262 // 1. a "header" line 1263 // 2. an actual row of data (in the Intervals variable) 1264 1265 // 'j' needs to go PAST the viewport if the last row is overlapping and spans beyond the viewport. 1266 for (let j = j0; j < tvh; j++) { 1267 1268 if (j >= this.VisualLineStartIdx) { should_render = true; } 1269 if (y > height) { should_render = false; } 1270 1271 const tmp = this.VisualLineIndexToDataLineIndex(j); 1272 if (tmp == undefined) break; 1273 const data_line_idx = tmp[0]; 1274 const visual_line_offset_within_data_line = tmp[1]; 1275 1276 const should_render_title = (data_line_idx != last_data_line_idx) || 1277 (j == this.VisualLineStartIdx); // The first visible line should always be drawn 1278 last_data_line_idx = data_line_idx; 1279 1280 if (should_render_title && data_line_idx != -999 && should_render) { // Only draw line title and histogram per data line index not visual line index 1281 ctx.textBaseline = 'middle'; 1282 ctx.textAlign = 'right'; 1283 let desc_width = 0; 1284 if (NetFnCmdToDescription[this.Titles[data_line_idx].title] != undefined) { 1285 let desc = ' (' + NetFnCmdToDescription[this.Titles[data_line_idx].title] + ')'; 1286 desc_width = ctx.measureText(desc).width; 1287 ctx.fillStyle = '#888'; // Grey 1288 ctx.fillText(desc, LEFT_MARGIN - 3, y); 1289 } 1290 1291 1292 // Plot histogram 1293 if (this.IsTimeDistributionEnabled == true) { 1294 const t = this.Titles[data_line_idx].title; 1295 if (GetHistoryHistogram()[t] != undefined) { 1296 if (this.IpmiVizHistogramImageData[t] == undefined) { 1297 let tmp = RenderHistogramForImageData(ctx, t); 1298 this.IpmiVizHistogramImageData[t] = tmp[0]; 1299 HistogramThresholds[t] = tmp[1]; 1300 } 1301 this.RenderHistogram(ctx, t, HISTOGRAM_X, y); 1302 ctx.textAlignment = 'right'; 1303 } else { 1304 } 1305 } 1306 1307 // If is HEADER: do not draw here, darw in do_RenderHeader() 1308 if (this.Titles[data_line_idx].header == false) { 1309 ctx.textAlignment = 'right'; 1310 ctx.textBaseline = 'middle'; 1311 ctx.fillStyle = '#000000'; // Revert to Black 1312 ctx.strokeStyle = '#000000'; 1313 let tj_draw = this.Titles[data_line_idx].title; 1314 const title_disp_length_limit = this.GetTitleWidthLimit(); 1315 if (tj_draw != undefined && tj_draw.length > title_disp_length_limit) { 1316 tj_draw = tj_draw.substr(0, title_disp_length_limit) + '...' 1317 } 1318 ctx.fillText(tj_draw, LEFT_MARGIN - 3 - desc_width, y); 1319 } 1320 } else if (should_render_title && data_line_idx == -999) { 1321 continue; 1322 } 1323 1324 let numOverflowEntriesToTheLeft = 0; // #entries below the lower bound 1325 let numOverflowEntriesToTheRight = 1326 0; // #entries beyond the upper bound 1327 let numVisibleRequestsCurrLine = 0; // #entries visible 1328 let totalSecsCurrLine = 0; // Total duration in seconds 1329 let numFailedRequestsCurrLine = 0; 1330 1331 const intervals_idxes = this.Titles[data_line_idx].intervals_idxes; 1332 1333 let intervals_j = undefined; 1334 if (intervals_idxes.length == 1) { 1335 intervals_j = this.Intervals[intervals_idxes[0]]; 1336 } 1337 1338 // Draw the contents in the set of intervals 1339 // The drawing method depends on whether this data line is a header or not 1340 1341 // Save the context for reference for the rendering routines 1342 let vars = { 1343 "theHoveredReq": theHoveredReq, 1344 "theHoveredInterval": theHoveredInterval, 1345 "numIntersected": numIntersected, 1346 "numOverflowEntriesToTheLeft": numOverflowEntriesToTheLeft, 1347 "numOverflowEntriesToTheRight": numOverflowEntriesToTheRight, 1348 "currHighlightedReqs": currHighlightedReqs, 1349 "totalSecondsPerLine": totalSecondsPerLine, 1350 "highlightedInterval": highlightedInterval, 1351 "numVisibleRequestsCurrLine": numVisibleRequestsCurrLine, 1352 "totalSecsCurrLine": totalSecsCurrLine, 1353 } // Emulate a reference 1354 1355 let dy0 = y - LINE_HEIGHT / 2, dy1 = y + LINE_HEIGHT / 2; 1356 if (this.Titles[data_line_idx].header == false) { 1357 if (intervals_j != undefined) { 1358 this.do_RenderIntervals(ctx, intervals_j, j, dy0, dy1, 1359 data_line_idx, visual_line_offset_within_data_line, isAggregateSelection, vars, should_render); 1360 } 1361 } else { 1362 this.do_RenderHeader(ctx, this.Titles[data_line_idx], 1363 j, dy0, dy1, 1364 data_line_idx, visual_line_offset_within_data_line, isAggregateSelection, vars, should_render); 1365 } 1366 1367 // Update the context variables with updated values 1368 theHoveredReq = vars.theHoveredReq; 1369 theHoveredInterval = vars.theHoveredInterval; 1370 numIntersected = vars.numIntersected; 1371 numOverflowEntriesToTheLeft = vars.numOverflowEntriesToTheLeft; 1372 numOverflowEntriesToTheRight = vars.numOverflowEntriesToTheRight; 1373 currHighlightedReqs = vars.currHighlightedReqs; 1374 totalSecondsPerLine = vars.totalSecondsPerLine; 1375 highlightedInterval = vars.highlightedInterval; 1376 numVisibleRequestsCurrLine = vars.numVisibleRequestsCurrLine; 1377 totalSecsCurrLine = vars.totalSecsCurrLine; 1378 1379 // Triangle markers for entries outside of the viewport 1380 { 1381 const PAD = 2, H = LINE_SPACING; 1382 if (this.MouseState.hoveredVisibleLineIndex + this.VisualLineStartIdx == data_line_idx && 1383 this.MouseState.hoveredSide == 'left') { 1384 ctx.fillStyle = '#0000FF'; 1385 } else { 1386 ctx.fillStyle = 'rgba(128,128,0,0.5)'; 1387 } 1388 if (numOverflowEntriesToTheLeft > 0) { 1389 ctx.beginPath(); 1390 ctx.moveTo(LEFT_MARGIN + PAD + H / 2, y - H / 2); 1391 ctx.lineTo(LEFT_MARGIN + PAD, y); 1392 ctx.lineTo(LEFT_MARGIN + PAD + H / 2, y + H / 2); 1393 ctx.closePath(); 1394 ctx.fill(); 1395 ctx.textAlign = 'left'; 1396 ctx.textBaseline = 'center'; 1397 ctx.fillText( 1398 '+' + numOverflowEntriesToTheLeft, 1399 LEFT_MARGIN + 2 * PAD + H / 2, y); 1400 } 1401 1402 if (this.MouseState.hoveredVisibleLineIndex + this.VisualLineStartIdx == j && 1403 this.MouseState.hoveredSide == 'right') { 1404 ctx.fillStyle = '#0000FF'; 1405 } else { 1406 ctx.fillStyle = 'rgba(128,128,0,0.5)'; 1407 } 1408 if (numOverflowEntriesToTheRight > 0) { 1409 ctx.beginPath(); 1410 ctx.moveTo(RIGHT_MARGIN - PAD - H / 2, y - H / 2); 1411 ctx.lineTo(RIGHT_MARGIN - PAD, y); 1412 ctx.lineTo(RIGHT_MARGIN - PAD - H / 2, y + H / 2); 1413 ctx.closePath(); 1414 ctx.fill(); 1415 ctx.textAlign = 'right'; 1416 ctx.fillText( 1417 '+' + numOverflowEntriesToTheRight, 1418 RIGHT_MARGIN - 2 * PAD - H / 2, y); 1419 } 1420 } 1421 1422 if (should_render) 1423 y = y + LINE_SPACING; 1424 1425 // Should aggregate. 1426 if (!(data_line_idx in numVisibleRequestsPerLine)) { numVisibleRequestsPerLine[data_line_idx] = 0; } 1427 numVisibleRequestsPerLine[data_line_idx] += numVisibleRequestsCurrLine; 1428 1429 if (!(data_line_idx in numFailedRequestsPerLine)) { numFailedRequestsPerLine[data_line_idx] = 0; } 1430 numFailedRequestsPerLine[data_line_idx] += numFailedRequestsCurrLine; 1431 1432 if (!(data_line_idx in totalSecondsPerLine)) { totalSecondsPerLine[data_line_idx] = 0; } 1433 totalSecondsPerLine[data_line_idx] += totalSecsCurrLine; 1434 1435 title_end_idx = j; 1436 1437 if (y > height) { 1438 // Make sure we don't miss the entry count of the rows beyond the viewport 1439 if (visual_line_offset_within_data_line == 0) { 1440 break; 1441 } 1442 } 1443 } 1444 1445 { 1446 let nbreaks = this.TotalVisualHeight(); 1447 // Draw a scroll bar on the left 1448 if (!(title_start_idx == 0 && title_end_idx == nbreaks - 1)) { 1449 1450 const y0 = title_start_idx * height / nbreaks; 1451 const y1 = (1 + title_end_idx) * height / nbreaks; 1452 1453 let highlighted = false; 1454 const THRESH = 8; 1455 if (this.MouseState.IsDraggingScrollBar()) { 1456 highlighted = true; 1457 } 1458 this.ScrollBarState.highlighted = highlighted; 1459 1460 // If not dragging, let title_start_idx drive y0 and y1, else let the 1461 // user's input drive y0 and y1 and title_start_idx 1462 if (!this.MouseState.IsDraggingScrollBar()) { 1463 this.ScrollBarState.y0 = y0; 1464 this.ScrollBarState.y1 = y1; 1465 } 1466 1467 if (highlighted) { 1468 ctx.fillStyle = "#FF3"; 1469 } else { 1470 ctx.fillStyle = this.AccentColor; 1471 } 1472 ctx.fillRect(0, y0, SCROLL_BAR_WIDTH, y1 - y0); 1473 1474 } else { 1475 this.ScrollBarState.y0 = undefined; 1476 this.ScrollBarState.y1 = undefined; 1477 this.ScrollBarState.highlighted = false; 1478 } 1479 } 1480 1481 // Draw highlighted sections for the histograms 1482 if (this.IsTimeDistributionEnabled) { 1483 y = YBEGIN; 1484 for (let j = this.TitleStartIdx; j < this.Intervals.length; j++) { 1485 if (this.IpmiVizHistHighlighted[this.Titles[data_line_idx].title] != undefined) { 1486 let entry = HistogramThresholds[this.Titles[data_line_idx].title]; 1487 const theSet = 1488 Array.from(this.IpmiVizHistHighlighted[this.Titles[data_line_idx].title]); 1489 for (let i = 0; i < theSet.length; i++) { 1490 bidx = theSet[i]; 1491 if (entry != undefined) { 1492 if (bidx < entry[0][0]) { 1493 if (bidx < 0) { 1494 bidx = 0; 1495 } 1496 ctx.fillStyle = 'rgba(0, 255, 0, 0.3)'; 1497 } else if (bidx > entry[1][0]) { 1498 if (bidx > 1) { 1499 bidx = 1; 1500 } 1501 ctx.fillStyle = 'rgba(255,0,0,0.3)'; 1502 } else { 1503 ctx.fillStyle = 'rgba(0,0,255,0.3)'; 1504 } 1505 } else { 1506 ctx.fillStyle = 'rgba(0,0,255,0.3)'; 1507 } 1508 const dx = HISTOGRAM_X - HISTOGRAM_W / 2 + HISTOGRAM_W * bidx; 1509 1510 const r = HISTOGRAM_H * 0.17; 1511 ctx.beginPath(); 1512 ctx.ellipse(dx, y, r, r, 0, 0, 3.14159 * 2); 1513 ctx.fill(); 1514 } 1515 } 1516 y += LINE_SPACING; 1517 } 1518 } 1519 1520 // Render number of visible requests versus totals 1521 ctx.textAlign = 'left'; 1522 ctx.textBaseline = 'top'; 1523 let totalOccs = 0, totalSecs = 0; 1524 if (this.IsHighlighted()) { 1525 ctx.fillStyle = '#00F'; 1526 ctx.fillText('# / time', 3, TEXT_Y0); 1527 ctx.fillText('in selection', 3, TEXT_Y0 + LINE_SPACING - 2); 1528 } else { 1529 ctx.fillStyle = '#000'; 1530 ctx.fillText('# / time', 3, TEXT_Y0); 1531 ctx.fillText('in viewport', 3, TEXT_Y0 + LINE_SPACING - 2); 1532 } 1533 1534 let timeDesc = ''; 1535 ctx.textBaseline = 'middle'; 1536 last_data_line_idx = -999; 1537 1538 for (let j = this.VisualLineStartIdx, i = 0; 1539 j < tvh && (YBEGIN + i*LINE_SPACING)<height; j++, i++) { 1540 const x = this.VisualLineIndexToDataLineIndex(j); 1541 if (x == undefined) break; 1542 const data_line_idx = x[0]; 1543 if (data_line_idx == undefined) break; 1544 if (data_line_idx != last_data_line_idx) { 1545 let y1 = YBEGIN + LINE_SPACING * (i); 1546 let totalSeconds = totalSecondsPerLine[data_line_idx]; 1547 if (totalSeconds < 1) { 1548 timeDesc = (totalSeconds * 1000.0).toFixed(toFixedPrecision) + 'ms'; 1549 } else if (totalSeconds != undefined) { 1550 timeDesc = totalSeconds.toFixed(toFixedPrecision) + 's'; 1551 } else { 1552 timeDesc = "???" 1553 } 1554 1555 const n0 = numVisibleRequestsPerLine[data_line_idx]; 1556 const n1 = numFailedRequestsPerLine[data_line_idx]; 1557 let txt = ''; 1558 if (n1 > 0) { 1559 txt = '' + n0 + '+' + n1 + ' / ' + timeDesc; 1560 } else { 1561 txt = '' + n0 + ' / ' + timeDesc; 1562 } 1563 1564 const tw = ctx.measureText(txt).width; 1565 const PAD = 8; 1566 1567 ctx.fillStyle = '#000'; 1568 ctx.fillText(txt, 3, y1); 1569 totalOccs += numVisibleRequestsPerLine[data_line_idx]; 1570 totalSecs += totalSeconds; 1571 } 1572 last_data_line_idx = data_line_idx; 1573 } 1574 1575 // This does not get displayed correctly, so disabling for now 1576 //timeDesc = ''; 1577 //if (totalSecs < 1) { 1578 // timeDesc = '' + (totalSecs * 1000).toFixed(toFixedPrecision) + 'ms'; 1579 //} else { 1580 // timeDesc = '' + totalSecs.toFixed(toFixedPrecision) + 's'; 1581 //} 1582 1583 //ctx.fillText('Sum:', 3, y + 2 * LINE_SPACING); 1584 //ctx.fillText('' + totalOccs + ' / ' + timeDesc, 3, y + 3 * LINE_SPACING); 1585 1586 // Update highlighted requests 1587 if (this.IsHighlightDirty) { 1588 this.HighlightedRequests = currHighlightedReqs; 1589 this.IsHighlightDirty = false; 1590 1591 // Todo: This callback will be different for the DBus pane 1592 OnHighlightedChanged(HighlightedRequests); 1593 } 1594 1595 // Render highlight statistics 1596 if (this.IsHighlighted()) { 1597 ctx.fillStyle = 'rgba(128,128,255,0.5)'; 1598 let x0 = MapXCoord( 1599 t0, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 1600 this.UpperBoundTime); 1601 let x1 = MapXCoord( 1602 t1, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 1603 this.UpperBoundTime); 1604 ctx.fillRect(x0, 0, x1 - x0, height); 1605 1606 let label0 = '' + t0.toFixed(toFixedPrecision) + 's'; 1607 let label1 = '' + t1.toFixed(toFixedPrecision) + 's'; 1608 let width0 = ctx.measureText(label0).width; 1609 let width1 = ctx.measureText(label1).width; 1610 let dispWidth = x1 - x0; 1611 // Draw time marks outside or inside? 1612 ctx.fillStyle = '#0000FF'; 1613 ctx.textBaseline = 'top'; 1614 if (dispWidth > width0 + width1) { 1615 ctx.textAlign = 'left'; 1616 ctx.fillText(label0, x0, LINE_SPACING + TEXT_Y0); 1617 ctx.textAlign = 'right'; 1618 ctx.fillText(label1, x1, LINE_SPACING + TEXT_Y0); 1619 } else { 1620 ctx.textAlign = 'right'; 1621 ctx.fillText(label0, x0, LINE_SPACING + TEXT_Y0); 1622 ctx.textAlign = 'left'; 1623 ctx.fillText(label1, x1, LINE_SPACING + TEXT_Y0); 1624 } 1625 1626 // This was calculated earlier 1627 ctx.textAlign = 'center'; 1628 label1 = 'Duration: ' + (t1 - t0).toFixed(toFixedPrecision) + 's'; 1629 ctx.fillText(label1, (x0 + x1) / 2, height - LINE_SPACING * 2); 1630 } 1631 1632 // Hovering cursor 1633 // Only draw when the mouse is not over any hotizontal scroll bar 1634 let should_hide_cursor = false; 1635 1636 if (this.MouseState.hoveredSide == "top_horizontal_scrollbar" || 1637 this.MouseState.hoveredSide == "bottom_horizontal_scrollbar") { 1638 should_hide_cursor = true; 1639 } 1640 this.linked_views.forEach((v) => { 1641 if (v.MouseState.hoveredSide == "top_horizontal_scrollbar" || 1642 v.MouseState.hoveredSide == "bottom_horizontal_scrollbar") { 1643 should_hide_cursor = true; 1644 } 1645 }) 1646 1647 if (this.MouseState.hovered == true && 1648 this.MouseState.hoveredSide == undefined && 1649 should_hide_cursor == false) { 1650 ctx.beginPath(); 1651 ctx.strokeStyle = '#0000FF'; 1652 ctx.lineWidth = 1; 1653 if (this.IsHighlighted()) { 1654 ctx.moveTo(this.MouseState.x, 0); 1655 ctx.lineTo(this.MouseState.x, height); 1656 } else { 1657 ctx.moveTo(this.MouseState.x, LINE_SPACING * 2); 1658 ctx.lineTo(this.MouseState.x, height - LINE_SPACING * 2); 1659 } 1660 ctx.stroke(); 1661 1662 if (this.IsHighlighted() == false) { 1663 let dispWidth = this.MouseState.x - LEFT_MARGIN; 1664 let label = '' + 1665 this.MouseXToTimestamp(this.MouseState.x) 1666 .toFixed(toFixedPrecision) + 1667 's'; 1668 let width0 = ctx.measureText(label).width; 1669 ctx.fillStyle = '#0000FF'; 1670 ctx.textBaseline = 'bottom'; 1671 ctx.textAlign = 'center'; 1672 ctx.fillText(label, this.MouseState.x, height - LINE_SPACING); 1673 ctx.textBaseline = 'top'; 1674 ctx.fillText(label, this.MouseState.x, LINE_SPACING + TEXT_Y0); 1675 } 1676 } 1677 1678 // Tooltip box next to hovered entry 1679 if (theHoveredReq !== undefined) { 1680 this.RenderToolTip( 1681 ctx, theHoveredReq, theHoveredInterval, toFixedPrecision, height); 1682 } 1683 } // End IsCanvasDirty 1684 } 1685}; 1686 1687// The extended classes have their own way of drawing popups for hovered entries 1688class IPMITimelineView extends TimelineView { 1689 RenderToolTip( 1690 ctx, theHoveredReq, theHoveredInterval, toFixedPrecision, height) { 1691 if (theHoveredReq == undefined) { 1692 return; 1693 } 1694 const PAD = 2, DELTA_Y = 14; 1695 1696 let labels = []; 1697 let netFn = theHoveredReq[0]; 1698 let cmd = theHoveredReq[1]; 1699 let t0 = theHoveredInterval[0]; 1700 let t1 = theHoveredInterval[1]; 1701 1702 labels.push('Netfn and CMD : (' + netFn + ', ' + cmd + ')'); 1703 let key = netFn + ', ' + cmd; 1704 1705 if (NetFnCmdToDescription[key] != undefined) { 1706 labels.push('Description : ' + NetFnCmdToDescription[key]); 1707 } 1708 1709 if (theHoveredReq.offset != undefined) { 1710 labels.push('Offset : ' + theHoveredReq.offset); 1711 } 1712 1713 let req = theHoveredReq[4]; 1714 labels.push('Request Data : ' + req.length + ' bytes'); 1715 if (req.length > 0) { 1716 labels.push('Hex : ' + ToHexString(req, '', ' ')); 1717 labels.push('ASCII : ' + ToASCIIString(req)); 1718 } 1719 let resp = theHoveredReq[5]; 1720 labels.push('Response Data : ' + theHoveredReq[5].length + ' bytes'); 1721 if (resp.length > 0) { 1722 labels.push('Hex : ' + ToHexString(resp, '', ' ')); 1723 labels.push('ASCII : ' + ToASCIIString(resp)); 1724 } 1725 labels.push('Start : ' + t0.toFixed(toFixedPrecision) + 's'); 1726 labels.push('End : ' + t1.toFixed(toFixedPrecision) + 's'); 1727 labels.push('Duration : ' + ((t1 - t0) * 1000).toFixed(3) + 'ms'); 1728 1729 1730 let w = 1, h = LINE_SPACING * labels.length + 2 * PAD; 1731 for (let i = 0; i < labels.length; i++) { 1732 w = Math.max(w, ctx.measureText(labels[i]).width); 1733 } 1734 let dy = this.MouseState.y + DELTA_Y; 1735 if (dy + h > height) { 1736 dy = height - h; 1737 } 1738 let dx = this.MouseState.x; 1739 if (RIGHT_MARGIN - dx < w) dx -= (w + 2 * PAD); 1740 1741 ctx.fillStyle = 'rgba(0,0,0,0.5)'; 1742 ctx.fillRect(dx, dy, w + 2 * PAD, h); 1743 1744 ctx.textAlign = 'left'; 1745 ctx.textBaseline = 'middle'; 1746 ctx.fillStyle = '#FFFFFF'; 1747 for (let i = 0; i < labels.length; i++) { 1748 ctx.fillText( 1749 labels[i], dx + PAD, dy + PAD + i * LINE_SPACING + LINE_SPACING / 2); 1750 } 1751 } 1752}; 1753 1754class DBusTimelineView extends TimelineView { 1755 RenderToolTip( 1756 ctx, theHoveredReq, theHoveredInterval, toFixedPrecision, height) { 1757 if (theHoveredReq == undefined) { 1758 return; 1759 } 1760 const PAD = 2, DELTA_Y = 14; 1761 1762 let labels = []; 1763 let msg_type = theHoveredReq[0]; 1764 let serial = theHoveredReq[2]; 1765 let sender = theHoveredReq[3]; 1766 let destination = theHoveredReq[4]; 1767 let path = theHoveredReq[5]; 1768 let iface = theHoveredReq[6]; 1769 let member = theHoveredReq[7]; 1770 1771 let t0 = theHoveredInterval[0]; 1772 let t1 = theHoveredInterval[1]; 1773 1774 labels.push('Message type: ' + msg_type); 1775 labels.push('Serial : ' + serial); 1776 labels.push('Sender : ' + sender); 1777 labels.push('Destination : ' + destination); 1778 labels.push('Path : ' + path); 1779 labels.push('Interface : ' + iface); 1780 labels.push('Member : ' + member); 1781 1782 let w = 1, h = LINE_SPACING * labels.length + 2 * PAD; 1783 for (let i = 0; i < labels.length; i++) { 1784 w = Math.max(w, ctx.measureText(labels[i]).width); 1785 } 1786 let dy = this.MouseState.y + DELTA_Y; 1787 if (dy + h > height) { 1788 dy = height - h; 1789 } 1790 let dx = this.MouseState.x; 1791 if (RIGHT_MARGIN - dx < w) dx -= (w + 2 * PAD); 1792 1793 ctx.fillStyle = 'rgba(0,0,0,0.5)'; 1794 ctx.fillRect(dx, dy, w + 2 * PAD, h); 1795 1796 ctx.textAlign = 'left'; 1797 ctx.textBaseline = 'middle'; 1798 ctx.fillStyle = '#FFFFFF'; 1799 for (let i = 0; i < labels.length; i++) { 1800 ctx.fillText( 1801 labels[i], dx + PAD, dy + PAD + i * LINE_SPACING + LINE_SPACING / 2); 1802 } 1803 } 1804}; 1805 1806class BoostASIOHandlerTimelineView extends TimelineView { 1807 RenderToolTip( 1808 ctx, theHoveredReq, theHoveredInterval, toFixedPrecision, height) { 1809 if (theHoveredReq == undefined) { 1810 return; 1811 } 1812 const PAD = 2, DELTA_Y = 14; 1813 1814 let labels = []; 1815 let create_time = theHoveredReq[2]; 1816 let enter_time = theHoveredReq[3]; 1817 let exit_time = theHoveredReq[4]; 1818 let desc = theHoveredReq[5]; 1819 1820 let t0 = theHoveredInterval[0]; 1821 let t1 = theHoveredInterval[1]; 1822 1823 labels.push('Creation time: ' + create_time); 1824 labels.push('Entry time : ' + enter_time); 1825 labels.push('Exit time : ' + exit_time); 1826 labels.push('Creation->Entry : ' + (enter_time - create_time)); 1827 labels.push('Entry->Exit : ' + (exit_time - enter_time)); 1828 labels.push('Description : ' + desc); 1829 1830 let w = 1, h = LINE_SPACING * labels.length + 2 * PAD; 1831 for (let i = 0; i < labels.length; i++) { 1832 w = Math.max(w, ctx.measureText(labels[i]).width); 1833 } 1834 let dy = this.MouseState.y + DELTA_Y; 1835 if (dy + h > height) { 1836 dy = height - h; 1837 } 1838 let dx = this.MouseState.x; 1839 if (RIGHT_MARGIN - dx < w) dx -= (w + 2 * PAD); 1840 1841 ctx.fillStyle = 'rgba(0,0,0,0.5)'; 1842 ctx.fillRect(dx, dy, w + 2 * PAD, h); 1843 1844 ctx.textAlign = 'left'; 1845 ctx.textBaseline = 'middle'; 1846 ctx.fillStyle = '#FFFFFF'; 1847 for (let i = 0; i < labels.length; i++) { 1848 ctx.fillText( 1849 labels[i], dx + PAD, dy + PAD + i * LINE_SPACING + LINE_SPACING / 2); 1850 } 1851 } 1852} 1853