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 // If highlighted area changed, update the info panel 631 UpdateHighlightedMessagesInfoPanel(); 632 } 633 634 UnhighlightIfEmpty() { 635 if (this.HighlightedRegion.t0 == this.HighlightedRegion.t1) { 636 this.Unhighlight(); 637 this.IsCanvasDirty = true; 638 return true; 639 } else 640 return false; 641 } 642 643 OnMouseWheel(event) { 644 event.preventDefault(); 645 const v = this; 646 647 let is_mouse_on_horizontal_scrollbar = false; 648 if (this.MouseState.y > 0 && this.MouseState.y < TOP_HORIZONTAL_SCROLLBAR_HEIGHT) 649 is_mouse_on_horizontal_scrollbar = true; 650 if (this.MouseState.y > this.Canvas.height - BOTTOM_HORIZONTAL_SCROLLBAR_HEIGHT && 651 this.MouseState.y < this.Canvas.height) 652 is_mouse_on_horizontal_scrollbar = true; 653 654 if (/*v.IsMouseOverTimeline()*/ is_mouse_on_horizontal_scrollbar) { 655 let dz = 0; 656 if (event.deltaY > 0) { // Scroll down, zoom out 657 dz = -0.3; 658 } else if (event.deltaY < 0) { // Scroll up, zoom in 659 dz = 0.3; 660 } 661 v.Zoom(dz, v.MouseXToTimestamp(v.MouseState.x)); 662 } else { 663 if (event.deltaY > 0) { 664 v.ScrollY(1); 665 } else if (event.deltaY < 0) { 666 v.ScrollY(-1); 667 } 668 } 669 } 670 671 ScrollY(delta) { 672 this.VisualLineStartIdx += delta; 673 if (this.VisualLineStartIdx < 0) { 674 this.VisualLineStartIdx = 0; 675 } else if (this.VisualLineStartIdx >= this.TotalVisualHeight()) { 676 this.VisualLineStartIdx = this.TotalVisualHeight() - 1; 677 } 678 } 679 680 // This function is called in Render to draw a line of Intervals. 681 // It is made into its own function for brevity in Render(). 682 // It depends on too much context so it doesn't look very clean though 683 do_RenderIntervals(ctx, intervals_j, j, dy0, dy1, 684 data_line_idx, visual_line_offset_within_data_line, 685 isAggregateSelection, 686 vars, 687 is_in_viewport) { 688 // To reduce the number of draw calls while preserve the accuracy in 689 // the visual presentation, combine rectangles that are within 1 pixel 690 // into one 691 let last_dx_begin = LEFT_MARGIN; 692 let last_dx_end = LEFT_MARGIN; 693 694 for (let i = 0; i < intervals_j.length; i++) { 695 let lb = intervals_j[i][0], ub = intervals_j[i][1]; 696 const yoffset = intervals_j[i][4]; 697 if (yoffset != visual_line_offset_within_data_line) 698 continue; 699 if (lb > ub) 700 continue; // Unmatched (only enter & no exit timestamp) 701 702 let isHighlighted = false; 703 let durationUsec = 704 (intervals_j[i][1] - intervals_j[i][0]) * 1000000; 705 let lbub = [lb, ub]; 706 if (this.IsHighlighted()) { 707 if (IsIntersected(lbub, vars.highlightedInterval)) { 708 vars.numIntersected++; 709 isHighlighted = true; 710 vars.currHighlightedReqs.push(intervals_j[i][2]); // TODO: change the name to avoid confusion with HighlightedMessages 711 } 712 } 713 714 if (ub < this.LowerBoundTime) { 715 vars.numOverflowEntriesToTheLeft++; 716 continue; 717 } 718 if (lb > this.UpperBoundTime) { 719 vars.numOverflowEntriesToTheRight++; 720 continue; 721 } 722 // Failed request 723 if (ub == undefined && lb < this.UpperBoundTime) { 724 vars.numOverflowEntriesToTheLeft++; 725 continue; 726 } 727 728 let dx0 = MapXCoord( 729 lb, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 730 this.UpperBoundTime), 731 dx1 = MapXCoord( 732 ub, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 733 this.UpperBoundTime); 734 735 dx0 = Math.max(dx0, LEFT_MARGIN); 736 dx1 = Math.min(dx1, RIGHT_MARGIN); 737 let dw = Math.max(0, dx1 - dx0); 738 739 if (isHighlighted && is_in_viewport) { 740 ctx.fillStyle = 'rgba(128,128,255,0.5)'; 741 ctx.fillRect(dx0, dy0, dw, dy1 - dy0); 742 } 743 744 let isCurrentReqHovered = false; 745 // Intersect with mouse using pixel coordinates 746 747 // When the mouse position is within 4 pixels distance from an entry, consider 748 // the mouse to be over that entry and show the information popup 749 const X_TOLERANCE = 4; 750 751 if (vars.theHoveredReq == undefined && 752 IsIntersectedPixelCoords( 753 [dx0 - X_TOLERANCE, dx0 + dw + X_TOLERANCE], 754 [this.MouseState.x, this.MouseState.x]) && 755 IsIntersectedPixelCoords( 756 [dy0, dy1], [this.MouseState.y, this.MouseState.y])) { 757 ctx.fillStyle = 'rgba(255,255,0,0.5)'; 758 if (is_in_viewport) ctx.fillRect(dx0, dy0, dw, dy1 - dy0); 759 vars.theHoveredReq = intervals_j[i][2]; 760 vars.theHoveredInterval = intervals_j[i]; 761 isCurrentReqHovered = true; 762 } 763 764 ctx.lineWidth = 0.5; 765 766 767 // If this request is taking too long/is quick enough, use red/green 768 let entry = HistogramThresholds[this.Titles[data_line_idx].title]; 769 770 let isError = false; 771 if (intervals_j[i][3] == 'error') { 772 isError = true; 773 } 774 775 if (entry != undefined) { 776 if (entry[0][1] != undefined && durationUsec < entry[0][1]) { 777 ctx.strokeStyle = '#0F0'; 778 } else if ( 779 entry[1][1] != undefined && durationUsec > entry[1][1]) { 780 ctx.strokeStyle = '#A00'; 781 } else { 782 ctx.strokeStyle = '#000'; 783 } 784 } else { 785 ctx.strokeStyle = '#000'; 786 } 787 788 const duration = intervals_j[i][1] - intervals_j[i][0]; 789 if (!isNaN(duration)) { 790 if (is_in_viewport) { 791 if (isError) { 792 ctx.fillStyle = 'rgba(192, 128, 128, 0.8)'; 793 ctx.fillRect(dx0, dy0, dw, dy1 - dy0); 794 ctx.strokeStyle = 'rgba(192, 128, 128, 1)'; 795 } else { 796 ctx.fillStyle = undefined; 797 ctx.strokeStyle = '#000'; 798 } 799 } 800 801 // This keeps track of the current "cluster" of requests 802 // that might visually overlap (i.e less than 1 pixel wide). 803 // This can greatly reduce overdraw and keep render time under 804 // a reasonable bound. 805 if (!ShouldShowDebugInfo()) { 806 if (dx0+dw - last_dx_begin > 1 || 807 i == intervals_j.length - 1) { 808 if (is_in_viewport) { 809 ctx.strokeRect(last_dx_begin, dy0, 810 /*dx0+dw-last_dx_begin*/ 811 last_dx_end - last_dx_begin, // At least 1 pixel wide 812 dy1-dy0); 813 } 814 last_dx_begin = dx0; 815 } 816 } else { 817 if (is_in_viewport) { 818 ctx.strokeRect(dx0, dy0, dw, dy1 - dy0); 819 } 820 } 821 last_dx_end = dx0 + dw; 822 this.numVisibleRequests++; 823 } else { 824 // This entry has only a beginning and not an end 825 // perhaps the original method call did not return 826 if (is_in_viewport) { 827 if (isCurrentReqHovered) { 828 ctx.fillStyle = 'rgba(192, 192, 0, 0.8)'; 829 } else { 830 ctx.fillStyle = 'rgba(255, 128, 128, 0.8)'; 831 } 832 ctx.beginPath(); 833 ctx.arc(dx0, (dy0 + dy1) / 2, HISTOGRAM_H * 0.17, 0, 2 * Math.PI); 834 ctx.fill(); 835 } 836 } 837 838 839 // Affects whether this req will be reflected in the aggregate info 840 // section 841 if ((isAggregateSelection == false) || 842 (isAggregateSelection == true && isHighlighted == true)) { 843 if (!isNaN(duration)) { 844 vars.numVisibleRequestsCurrLine++; 845 vars.totalSecsCurrLine += duration; 846 } else { 847 vars.numFailedRequestsCurrLine++; 848 } 849 850 // If a histogram exists for Titles[j], process the highlighted 851 // histogram buckets 852 if (GetHistoryHistogram()[this.Titles[data_line_idx].title] != undefined) { 853 let histogramEntry = GetHistoryHistogram()[this.Titles[data_line_idx].title]; 854 let bucketInterval = (histogramEntry[1] - histogramEntry[0]) / 855 histogramEntry[2].length; 856 let bucketIndex = 857 Math.floor( 858 (durationUsec - histogramEntry[0]) / bucketInterval) / 859 histogramEntry[2].length; 860 861 if (this.IpmiVizHistHighlighted[this.Titles[data_line_idx].title] == undefined) { 862 this.IpmiVizHistHighlighted[this.Titles[data_line_idx].title] = new Set(); 863 } 864 let entry = this.IpmiVizHistHighlighted[this.Titles[data_line_idx].title]; 865 entry.add(bucketIndex); 866 } 867 } 868 } // end for (i=0 to interval_j.length-1) 869 870 if (!ShouldShowDebugInfo()) { 871 ctx.strokeRect(last_dx_begin, dy0, 872 /*dx0+dw-last_dx_begin*/ 873 last_dx_end - last_dx_begin, // At least 1 pixel wide 874 dy1-dy0); 875 } 876 } 877 878 // For the header: 879 do_RenderHeader(ctx, header, j, dy0, dy1, 880 data_line_idx, visual_line_offset_within_data_line, 881 isAggregateSelection, 882 vars, is_in_viewport) { 883 884 const dy = (dy0+dy1) / 2; 885 ctx.fillStyle = "rgba(192,192,255, 1)"; 886 887 ctx.strokeStyle = "rgba(192,192,255, 1)" 888 889 const title_text = header.title + " (" + header.intervals_idxes.length + ")"; 890 let skip_render = false; 891 892 ctx.save(); 893 894 if (this.HeaderCollapsed[header.title] == false) { // Expanded 895 const x0 = LEFT_MARGIN - LINE_HEIGHT; 896 if (is_in_viewport) { 897 ctx.fillRect(0, dy-LINE_HEIGHT/2, x0, LINE_HEIGHT); 898 899 ctx.beginPath(); 900 ctx.moveTo(x0, dy0); 901 ctx.lineTo(x0, dy1); 902 ctx.lineTo(x0 + LINE_HEIGHT, dy1); 903 ctx.fill(); 904 ctx.closePath(); 905 906 ctx.beginPath(); 907 ctx.lineWidth = 1.5; 908 ctx.moveTo(0, dy1); 909 ctx.lineTo(RIGHT_MARGIN, dy1); 910 ctx.stroke(); 911 ctx.closePath(); 912 913 ctx.fillStyle = '#003'; 914 ctx.textBaseline = 'center'; 915 ctx.textAlign = 'right'; 916 ctx.fillText(title_text, LEFT_MARGIN - LINE_HEIGHT, dy); 917 } 918 919 // Don't draw the timelines so visual clutter is reduced 920 skip_render = true; 921 } else { 922 const x0 = LEFT_MARGIN - LINE_HEIGHT / 2; 923 if (is_in_viewport) { 924 ctx.fillRect(0, dy-LINE_HEIGHT/2, x0, LINE_HEIGHT); 925 926 ctx.beginPath(); 927 ctx.lineWidth = 1.5; 928 ctx.moveTo(x0, dy0); 929 ctx.lineTo(x0 + LINE_HEIGHT/2, dy); 930 ctx.lineTo(x0, dy1); 931 ctx.closePath(); 932 ctx.fill(); 933 934 /* 935 ctx.beginPath(); 936 ctx.moveTo(0, dy); 937 ctx.lineTo(RIGHT_MARGIN, dy); 938 ctx.stroke(); 939 ctx.closePath(); 940 */ 941 942 ctx.fillStyle = '#003'; 943 ctx.textBaseline = 'center'; 944 ctx.textAlign = 'right'; 945 ctx.fillText(title_text, LEFT_MARGIN - LINE_HEIGHT, dy); 946 } 947 } 948 949 ctx.fillStyle = "rgba(160,120,255,0.8)"; 950 951 ctx.restore(); 952 953 // Draw the merged intervals 954 // Similar to drawing the actual messages in do_Render(), but no collision detection against the mouse, and no hovering tooltip processing involved 955 const merged_intervals = header.merged_intervals; 956 let dxx0 = undefined, dxx1 = undefined; 957 for (let i=0; i<merged_intervals.length; i++) { 958 const lb = merged_intervals[i][0], ub = merged_intervals[i][1], weight = merged_intervals[i][2]; 959 let duration = ub-lb; 960 let duration_usec = duration * 1000000; 961 const lbub = [lb, ub]; 962 963 let isHighlighted = false; 964 if (this.IsHighlighted()) { 965 if (IsIntersected(lbub, vars.highlightedInterval)) { 966 vars.numIntersected += weight; 967 isHighlighted = true; 968 } 969 } 970 971 if (ub < this.LowerBoundTime) continue; 972 if (lb > this.UpperBoundTime) continue; 973 974 // Render only if collapsed 975 if (!skip_render) { 976 let dx0 = MapXCoord( 977 lb, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 978 this.UpperBoundTime), 979 dx1 = MapXCoord( 980 ub, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 981 this.UpperBoundTime); 982 dx0 = Math.max(dx0, LEFT_MARGIN); 983 dx1 = Math.min(dx1, RIGHT_MARGIN); 984 let dw = Math.max(1, dx1 - dx0); // At least 1 pixel wide during rendering 985 986 // Draw this interval 987 //ctx.fillRect(dx0, dy0, dw, dy1-dy0); 988 if (dxx0 == undefined || dxx1 == undefined) { 989 dxx0 = dx0; 990 } 991 992 const MERGE_THRESH = 0.5; // Pixels 993 994 let should_draw = true; 995 if (dxx1 == undefined || dx0 < dxx1 + MERGE_THRESH) should_draw = false; 996 if (i == merged_intervals.length - 1) { 997 should_draw = true; 998 dxx1 = dx1 + MERGE_THRESH; 999 } 1000 1001 if (should_draw) { 1002 //console.log(dxx0 + ", " + dy0 + ", " + (dx1-dxx0) + ", " + LINE_HEIGHT); 1003 if (is_in_viewport) { 1004 ctx.fillRect(dxx0, dy0, dxx1-dxx0, LINE_HEIGHT); 1005 } 1006 dxx0 = undefined; dxx1 = undefined; 1007 } else { 1008 // merge 1009 dxx1 = dx1 + MERGE_THRESH; 1010 } 1011 } 1012 1013 if ((isAggregateSelection == false) || 1014 (isAggregateSelection == true && isHighlighted == true)) { 1015 vars.totalSecsCurrLine += duration; 1016 vars.numVisibleRequestsCurrLine += weight; 1017 } 1018 } 1019 } 1020 1021 Render(ctx) { 1022 // Wait for initialization 1023 if (this.Canvas == undefined) return; 1024 1025 // Update 1026 let toFixedPrecision = 2; 1027 const extent = this.UpperBoundTime - this.LowerBoundTime; 1028 { 1029 if (extent < 0.1) { 1030 toFixedPrecision = 4; 1031 } else if (extent < 1) { 1032 toFixedPrecision = 3; 1033 } 1034 } 1035 1036 let dx = this.CurrDeltaX; 1037 if (dx != 0) { 1038 if (this.CurrShiftFlag) dx *= 5; 1039 this.LowerBoundTime += dx * extent; 1040 this.UpperBoundTime += dx * extent; 1041 this.IsCanvasDirty = true; 1042 } 1043 1044 // Hovered interval for display 1045 let theHoveredReq = undefined; 1046 let theHoveredInterval = undefined; 1047 let currHighlightedReqs = []; 1048 1049 let dz = this.CurrDeltaZoom; 1050 this.Zoom(dz); 1051 this.UpdateAnimation(); 1052 1053 this.LastTimeLowerBound = this.LowerBoundTime; 1054 this.LastTimeUpperBound = this.UpperBoundTime; 1055 1056 if (this.IsCanvasDirty) { 1057 this.IsCanvasDirty = false; 1058 // Shorthand for HighlightedRegion.t{0,1} 1059 let t0 = undefined, t1 = undefined; 1060 1061 // Highlight 1062 let highlightedInterval = []; 1063 let numIntersected = 1064 0; // How many requests intersect with highlighted area 1065 if (this.IsHighlighted()) { 1066 t0 = Math.min(this.HighlightedRegion.t0, this.HighlightedRegion.t1); 1067 t1 = Math.max(this.HighlightedRegion.t0, this.HighlightedRegion.t1); 1068 highlightedInterval = [t0, t1]; 1069 } 1070 this.IpmiVizHistHighlighted = {}; 1071 1072 const width = this.Canvas.width; 1073 const height = this.Canvas.height; 1074 1075 ctx.globalCompositeOperation = 'source-over'; 1076 ctx.clearRect(0, 0, width, height); 1077 ctx.strokeStyle = '#000'; 1078 ctx.fillStyle = '#000'; 1079 ctx.lineWidth = 1; 1080 1081 ctx.font = '12px Monospace'; 1082 1083 // Highlight current line 1084 if (this.MouseState.hoveredVisibleLineIndex != undefined) { 1085 const hv_lidx = this.MouseState.hoveredVisibleLineIndex + this.VisualLineStartIdx; 1086 if (hv_lidx >= 0 && 1087 hv_lidx < this.Titles.length) { 1088 ctx.fillStyle = 'rgba(32,32,32,0.2)'; 1089 let dy = YBEGIN + LINE_SPACING * this.MouseState.hoveredVisibleLineIndex - 1090 LINE_SPACING / 2; 1091 ctx.fillRect(0, dy, RIGHT_MARGIN, LINE_SPACING); 1092 } 1093 } 1094 1095 // Draw highlighted background over time labels when the mouse is hovering over 1096 // the time axis 1097 ctx.fillStyle = "#FF9"; 1098 if (this.MouseState.hoveredSide == "top_horizontal_scrollbar") { 1099 ctx.fillRect(LEFT_MARGIN, 0, RIGHT_MARGIN-LEFT_MARGIN, TOP_HORIZONTAL_SCROLLBAR_HEIGHT); 1100 } else if (this.MouseState.hoveredSide == "bottom_horizontal_scrollbar") { 1101 ctx.fillRect(LEFT_MARGIN, height-BOTTOM_HORIZONTAL_SCROLLBAR_HEIGHT, RIGHT_MARGIN-LEFT_MARGIN, BOTTOM_HORIZONTAL_SCROLLBAR_HEIGHT); 1102 } 1103 1104 ctx.fillStyle = '#000'; 1105 // Time marks at the beginning & end of the visible range 1106 ctx.textBaseline = 'bottom'; 1107 ctx.textAlign = 'left'; 1108 ctx.fillText( 1109 '' + this.LowerBoundTime.toFixed(toFixedPrecision) + 's', 1110 LEFT_MARGIN + 3, height); 1111 ctx.textAlign = 'end'; 1112 ctx.fillText( 1113 '' + this.UpperBoundTime.toFixed(toFixedPrecision) + 's', 1114 RIGHT_MARGIN - 3, height); 1115 1116 ctx.textBaseline = 'top'; 1117 ctx.textAlign = 'left'; 1118 ctx.fillText( 1119 '' + this.LowerBoundTime.toFixed(toFixedPrecision) + 's', 1120 LEFT_MARGIN + 3, TEXT_Y0); 1121 ctx.textAlign = 'right'; 1122 ctx.fillText( 1123 '' + this.UpperBoundTime.toFixed(toFixedPrecision) + 's', 1124 RIGHT_MARGIN - 3, TEXT_Y0); 1125 1126 let y = YBEGIN; 1127 let numVisibleRequests = 0; 1128 1129 ctx.beginPath(); 1130 ctx.moveTo(LEFT_MARGIN, 0); 1131 ctx.lineTo(LEFT_MARGIN, height); 1132 ctx.stroke(); 1133 1134 ctx.beginPath(); 1135 ctx.moveTo(RIGHT_MARGIN, 0); 1136 ctx.lineTo(RIGHT_MARGIN, height); 1137 ctx.stroke(); 1138 1139 // Column Titles 1140 ctx.fillStyle = '#000'; 1141 let columnTitle = '(All requests)'; 1142 if (this.GroupByStr.length > 0) { 1143 columnTitle = this.GroupByStr; 1144 } 1145 ctx.textAlign = 'right'; 1146 ctx.textBaseline = 'top'; 1147 // Split into lines 1148 { 1149 let lines = this.ToLines(columnTitle, this.TitleDispLengthLimit) 1150 for (let i = 0; i < lines.length; i++) { 1151 ctx.fillText(lines[i], LEFT_MARGIN - 3, 3 + i * LINE_HEIGHT); 1152 } 1153 } 1154 1155 if (this.IsTimeDistributionEnabled) { 1156 // "Histogram" title 1157 ctx.fillStyle = '#000'; 1158 ctx.textBaseline = 'top'; 1159 ctx.textAlign = 'center'; 1160 ctx.fillText('Time Distribution', HISTOGRAM_X, TEXT_Y0); 1161 1162 ctx.textAlign = 'right' 1163 ctx.fillText('In dataset /', HISTOGRAM_X, TEXT_Y0 + LINE_SPACING - 2); 1164 1165 ctx.fillStyle = '#00F'; 1166 1167 ctx.textAlign = 'left' 1168 if (this.IsHighlighted()) { 1169 ctx.fillText( 1170 ' In selection', HISTOGRAM_X, TEXT_Y0 + LINE_SPACING - 2); 1171 } 1172 else { 1173 ctx.fillText(' In viewport', HISTOGRAM_X, TEXT_Y0 + LINE_SPACING - 2); 1174 } 1175 } 1176 1177 ctx.fillStyle = '#000'; 1178 1179 // Time Axis Breaks 1180 const breakWidths = [ 1181 86400, 10800, 3600, 1800, 1200, 600, 300, 120, 1182 60, 30, 10, 5, 2, 1, 0.5, 0.2, 1183 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.0005, 1184 0.0002, 0.0001, 0.00005, 0.00002, 0.00001 1185 ]; 1186 const BreakDrawLimit = 1000; // Only draw up to this many grid lines 1187 1188 let bidx = 0; 1189 while (bidx < breakWidths.length && 1190 breakWidths[bidx] > this.UpperBoundTime - this.LowerBoundTime) { 1191 bidx++; 1192 } 1193 let breakWidth = breakWidths[bidx + 1]; 1194 if (bidx < breakWidths.length) { 1195 let t2 = 0; // Cannot name as "t0" otherwise clash 1196 bidx = 0; 1197 while (bidx < breakWidths.length) { 1198 while (t2 + breakWidths[bidx] < this.LowerBoundTime) { 1199 t2 += breakWidths[bidx]; 1200 } 1201 if (t2 + breakWidths[bidx] >= this.LowerBoundTime && 1202 t2 + breakWidths[bidx] <= this.UpperBoundTime) { 1203 break; 1204 } 1205 bidx++; 1206 } 1207 let draw_count = 0; 1208 if (bidx < breakWidths.length) { 1209 for (; t2 < this.UpperBoundTime; t2 += breakWidth) { 1210 if (t2 > this.LowerBoundTime) { 1211 ctx.beginPath(); 1212 let dx = MapXCoord( 1213 t2, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 1214 this.UpperBoundTime); 1215 ctx.strokeStyle = '#C0C0C0'; 1216 ctx.moveTo(dx, 0); 1217 ctx.lineTo(dx, height); 1218 ctx.stroke(); 1219 ctx.closePath(); 1220 ctx.fillStyle = '#C0C0C0'; 1221 1222 ctx.textAlign = 'left'; 1223 ctx.textBaseline = 'bottom'; 1224 let label2 = t2.toFixed(toFixedPrecision) + 's'; 1225 let w = ctx.measureText(label2).width; 1226 if (dx + w > RIGHT_MARGIN) ctx.textAlign = 'right'; 1227 ctx.fillText(label2, dx, height); 1228 1229 ctx.textBaseline = 'top'; 1230 ctx.fillText(label2, dx, TEXT_Y0); 1231 1232 draw_count++; 1233 if (draw_count > BreakDrawLimit) break; 1234 } 1235 } 1236 } 1237 } 1238 1239 // Whether we aggregate selected requests or visible requests 1240 let isAggregateSelection = false; 1241 if (this.IsHighlighted()) isAggregateSelection = true; 1242 let numVisibleRequestsPerLine = {}; // DataLineIndex -> Count 1243 let numFailedRequestsPerLine = {}; 1244 let totalSecondsPerLine = {}; 1245 1246 // Range of Titles that were displayed 1247 let title_start_idx = this.VisualLineStartIdx, title_end_idx = title_start_idx; 1248 1249 const tvh = this.TotalVisualHeight(); 1250 1251 // This is used to handle Intervals that have overlapping entries 1252 let last_data_line_idx = -999;//this.VisualLineIndexToDataLineIndex(this.VisualLineStartIdx); 1253 1254 // 'j' denotes a line index; if the viewport starts from the middle of an overlapping series of 1255 // lines, 'j' will be rewinded to the first one in the series to make the counts correct. 1256 let j0 = this.VisualLineStartIdx; 1257 while (j0 > 0 && this.VisualLineIndexToDataLineIndex(j0)[1] > 0) { j0--; } 1258 1259 // If should_render is false, we're counting the entries outisde the viewport 1260 // If should_render is true, do the rendering 1261 let should_render = false; 1262 1263 // 'j' then iterates over the "visual rows" that need to be displayed. 1264 // A "visual row" might be one of: 1265 // 1. a "header" line 1266 // 2. an actual row of data (in the Intervals variable) 1267 1268 // 'j' needs to go PAST the viewport if the last row is overlapping and spans beyond the viewport. 1269 for (let j = j0; j < tvh; j++) { 1270 1271 if (j >= this.VisualLineStartIdx) { should_render = true; } 1272 if (y > height) { should_render = false; } 1273 1274 const tmp = this.VisualLineIndexToDataLineIndex(j); 1275 if (tmp == undefined) break; 1276 const data_line_idx = tmp[0]; 1277 const visual_line_offset_within_data_line = tmp[1]; 1278 1279 const should_render_title = (data_line_idx != last_data_line_idx) || 1280 (j == this.VisualLineStartIdx); // The first visible line should always be drawn 1281 last_data_line_idx = data_line_idx; 1282 1283 if (should_render_title && data_line_idx != -999 && should_render) { // Only draw line title and histogram per data line index not visual line index 1284 ctx.textBaseline = 'middle'; 1285 ctx.textAlign = 'right'; 1286 let desc_width = 0; 1287 if (NetFnCmdToDescription[this.Titles[data_line_idx].title] != undefined) { 1288 let desc = ' (' + NetFnCmdToDescription[this.Titles[data_line_idx].title] + ')'; 1289 desc_width = ctx.measureText(desc).width; 1290 ctx.fillStyle = '#888'; // Grey 1291 ctx.fillText(desc, LEFT_MARGIN - 3, y); 1292 } 1293 1294 1295 // Plot histogram 1296 if (this.IsTimeDistributionEnabled == true) { 1297 const t = this.Titles[data_line_idx].title; 1298 if (GetHistoryHistogram()[t] != undefined) { 1299 if (this.IpmiVizHistogramImageData[t] == undefined) { 1300 let tmp = RenderHistogramForImageData(ctx, t); 1301 this.IpmiVizHistogramImageData[t] = tmp[0]; 1302 HistogramThresholds[t] = tmp[1]; 1303 } 1304 this.RenderHistogram(ctx, t, HISTOGRAM_X, y); 1305 ctx.textAlignment = 'right'; 1306 } else { 1307 } 1308 } 1309 1310 // If is HEADER: do not draw here, darw in do_RenderHeader() 1311 if (this.Titles[data_line_idx].header == false) { 1312 ctx.textAlignment = 'right'; 1313 ctx.textBaseline = 'middle'; 1314 ctx.fillStyle = '#000000'; // Revert to Black 1315 ctx.strokeStyle = '#000000'; 1316 let tj_draw = this.Titles[data_line_idx].title; 1317 const title_disp_length_limit = this.GetTitleWidthLimit(); 1318 if (tj_draw != undefined && tj_draw.length > title_disp_length_limit) { 1319 tj_draw = tj_draw.substr(0, title_disp_length_limit) + '...' 1320 } 1321 ctx.fillText(tj_draw, LEFT_MARGIN - 3 - desc_width, y); 1322 } 1323 } else if (should_render_title && data_line_idx == -999) { 1324 continue; 1325 } 1326 1327 let numOverflowEntriesToTheLeft = 0; // #entries below the lower bound 1328 let numOverflowEntriesToTheRight = 1329 0; // #entries beyond the upper bound 1330 let numVisibleRequestsCurrLine = 0; // #entries visible 1331 let totalSecsCurrLine = 0; // Total duration in seconds 1332 let numFailedRequestsCurrLine = 0; 1333 1334 const intervals_idxes = this.Titles[data_line_idx].intervals_idxes; 1335 1336 let intervals_j = undefined; 1337 if (intervals_idxes.length == 1) { 1338 intervals_j = this.Intervals[intervals_idxes[0]]; 1339 } 1340 1341 // Draw the contents in the set of intervals 1342 // The drawing method depends on whether this data line is a header or not 1343 1344 // Save the context for reference for the rendering routines 1345 let vars = { 1346 "theHoveredReq": theHoveredReq, 1347 "theHoveredInterval": theHoveredInterval, 1348 "numIntersected": numIntersected, 1349 "numOverflowEntriesToTheLeft": numOverflowEntriesToTheLeft, 1350 "numOverflowEntriesToTheRight": numOverflowEntriesToTheRight, 1351 "currHighlightedReqs": currHighlightedReqs, 1352 "totalSecondsPerLine": totalSecondsPerLine, 1353 "highlightedInterval": highlightedInterval, 1354 "numVisibleRequestsCurrLine": numVisibleRequestsCurrLine, 1355 "totalSecsCurrLine": totalSecsCurrLine, 1356 } // Emulate a reference 1357 1358 let dy0 = y - LINE_HEIGHT / 2, dy1 = y + LINE_HEIGHT / 2; 1359 if (this.Titles[data_line_idx].header == false) { 1360 if (intervals_j != undefined) { 1361 this.do_RenderIntervals(ctx, intervals_j, j, dy0, dy1, 1362 data_line_idx, visual_line_offset_within_data_line, isAggregateSelection, vars, should_render); 1363 } 1364 } else { 1365 this.do_RenderHeader(ctx, this.Titles[data_line_idx], 1366 j, dy0, dy1, 1367 data_line_idx, visual_line_offset_within_data_line, isAggregateSelection, vars, should_render); 1368 } 1369 1370 // Update the context variables with updated values 1371 theHoveredReq = vars.theHoveredReq; 1372 theHoveredInterval = vars.theHoveredInterval; 1373 numIntersected = vars.numIntersected; 1374 numOverflowEntriesToTheLeft = vars.numOverflowEntriesToTheLeft; 1375 numOverflowEntriesToTheRight = vars.numOverflowEntriesToTheRight; 1376 currHighlightedReqs = vars.currHighlightedReqs; 1377 totalSecondsPerLine = vars.totalSecondsPerLine; 1378 highlightedInterval = vars.highlightedInterval; 1379 numVisibleRequestsCurrLine = vars.numVisibleRequestsCurrLine; 1380 totalSecsCurrLine = vars.totalSecsCurrLine; 1381 1382 // Triangle markers for entries outside of the viewport 1383 { 1384 const PAD = 2, H = LINE_SPACING; 1385 if (this.MouseState.hoveredVisibleLineIndex + this.VisualLineStartIdx == data_line_idx && 1386 this.MouseState.hoveredSide == 'left') { 1387 ctx.fillStyle = '#0000FF'; 1388 } else { 1389 ctx.fillStyle = 'rgba(128,128,0,0.5)'; 1390 } 1391 if (numOverflowEntriesToTheLeft > 0) { 1392 ctx.beginPath(); 1393 ctx.moveTo(LEFT_MARGIN + PAD + H / 2, y - H / 2); 1394 ctx.lineTo(LEFT_MARGIN + PAD, y); 1395 ctx.lineTo(LEFT_MARGIN + PAD + H / 2, y + H / 2); 1396 ctx.closePath(); 1397 ctx.fill(); 1398 ctx.textAlign = 'left'; 1399 ctx.textBaseline = 'center'; 1400 ctx.fillText( 1401 '+' + numOverflowEntriesToTheLeft, 1402 LEFT_MARGIN + 2 * PAD + H / 2, y); 1403 } 1404 1405 if (this.MouseState.hoveredVisibleLineIndex + this.VisualLineStartIdx == j && 1406 this.MouseState.hoveredSide == 'right') { 1407 ctx.fillStyle = '#0000FF'; 1408 } else { 1409 ctx.fillStyle = 'rgba(128,128,0,0.5)'; 1410 } 1411 if (numOverflowEntriesToTheRight > 0) { 1412 ctx.beginPath(); 1413 ctx.moveTo(RIGHT_MARGIN - PAD - H / 2, y - H / 2); 1414 ctx.lineTo(RIGHT_MARGIN - PAD, y); 1415 ctx.lineTo(RIGHT_MARGIN - PAD - H / 2, y + H / 2); 1416 ctx.closePath(); 1417 ctx.fill(); 1418 ctx.textAlign = 'right'; 1419 ctx.fillText( 1420 '+' + numOverflowEntriesToTheRight, 1421 RIGHT_MARGIN - 2 * PAD - H / 2, y); 1422 } 1423 } 1424 1425 if (should_render) 1426 y = y + LINE_SPACING; 1427 1428 // Should aggregate. 1429 if (!(data_line_idx in numVisibleRequestsPerLine)) { numVisibleRequestsPerLine[data_line_idx] = 0; } 1430 numVisibleRequestsPerLine[data_line_idx] += numVisibleRequestsCurrLine; 1431 1432 if (!(data_line_idx in numFailedRequestsPerLine)) { numFailedRequestsPerLine[data_line_idx] = 0; } 1433 numFailedRequestsPerLine[data_line_idx] += numFailedRequestsCurrLine; 1434 1435 if (!(data_line_idx in totalSecondsPerLine)) { totalSecondsPerLine[data_line_idx] = 0; } 1436 totalSecondsPerLine[data_line_idx] += totalSecsCurrLine; 1437 1438 title_end_idx = j; 1439 1440 if (y > height) { 1441 // Make sure we don't miss the entry count of the rows beyond the viewport 1442 if (visual_line_offset_within_data_line == 0) { 1443 break; 1444 } 1445 } 1446 } 1447 1448 { 1449 let nbreaks = this.TotalVisualHeight(); 1450 // Draw a scroll bar on the left 1451 if (!(title_start_idx == 0 && title_end_idx == nbreaks - 1)) { 1452 1453 const y0 = title_start_idx * height / nbreaks; 1454 const y1 = (1 + title_end_idx) * height / nbreaks; 1455 1456 let highlighted = false; 1457 const THRESH = 8; 1458 if (this.MouseState.IsDraggingScrollBar()) { 1459 highlighted = true; 1460 } 1461 this.ScrollBarState.highlighted = highlighted; 1462 1463 // If not dragging, let title_start_idx drive y0 and y1, else let the 1464 // user's input drive y0 and y1 and title_start_idx 1465 if (!this.MouseState.IsDraggingScrollBar()) { 1466 this.ScrollBarState.y0 = y0; 1467 this.ScrollBarState.y1 = y1; 1468 } 1469 1470 if (highlighted) { 1471 ctx.fillStyle = "#FF3"; 1472 } else { 1473 ctx.fillStyle = this.AccentColor; 1474 } 1475 ctx.fillRect(0, y0, SCROLL_BAR_WIDTH, y1 - y0); 1476 1477 } else { 1478 this.ScrollBarState.y0 = undefined; 1479 this.ScrollBarState.y1 = undefined; 1480 this.ScrollBarState.highlighted = false; 1481 } 1482 } 1483 1484 // Draw highlighted sections for the histograms 1485 if (this.IsTimeDistributionEnabled) { 1486 y = YBEGIN; 1487 for (let j = this.TitleStartIdx; j < this.Intervals.length; j++) { 1488 if (this.IpmiVizHistHighlighted[this.Titles[data_line_idx].title] != undefined) { 1489 let entry = HistogramThresholds[this.Titles[data_line_idx].title]; 1490 const theSet = 1491 Array.from(this.IpmiVizHistHighlighted[this.Titles[data_line_idx].title]); 1492 for (let i = 0; i < theSet.length; i++) { 1493 bidx = theSet[i]; 1494 if (entry != undefined) { 1495 if (bidx < entry[0][0]) { 1496 if (bidx < 0) { 1497 bidx = 0; 1498 } 1499 ctx.fillStyle = 'rgba(0, 255, 0, 0.3)'; 1500 } else if (bidx > entry[1][0]) { 1501 if (bidx > 1) { 1502 bidx = 1; 1503 } 1504 ctx.fillStyle = 'rgba(255,0,0,0.3)'; 1505 } else { 1506 ctx.fillStyle = 'rgba(0,0,255,0.3)'; 1507 } 1508 } else { 1509 ctx.fillStyle = 'rgba(0,0,255,0.3)'; 1510 } 1511 const dx = HISTOGRAM_X - HISTOGRAM_W / 2 + HISTOGRAM_W * bidx; 1512 1513 const r = HISTOGRAM_H * 0.17; 1514 ctx.beginPath(); 1515 ctx.ellipse(dx, y, r, r, 0, 0, 3.14159 * 2); 1516 ctx.fill(); 1517 } 1518 } 1519 y += LINE_SPACING; 1520 } 1521 } 1522 1523 // Render number of visible requests versus totals 1524 ctx.textAlign = 'left'; 1525 ctx.textBaseline = 'top'; 1526 let totalOccs = 0, totalSecs = 0; 1527 if (this.IsHighlighted()) { 1528 ctx.fillStyle = '#00F'; 1529 ctx.fillText('# / time', 3, TEXT_Y0); 1530 ctx.fillText('in selection', 3, TEXT_Y0 + LINE_SPACING - 2); 1531 } else { 1532 ctx.fillStyle = '#000'; 1533 ctx.fillText('# / time', 3, TEXT_Y0); 1534 ctx.fillText('in viewport', 3, TEXT_Y0 + LINE_SPACING - 2); 1535 } 1536 1537 let timeDesc = ''; 1538 ctx.textBaseline = 'middle'; 1539 last_data_line_idx = -999; 1540 1541 for (let j = this.VisualLineStartIdx, i = 0; 1542 j < tvh && (YBEGIN + i*LINE_SPACING)<height; j++, i++) { 1543 const x = this.VisualLineIndexToDataLineIndex(j); 1544 if (x == undefined) break; 1545 const data_line_idx = x[0]; 1546 if (data_line_idx == undefined) break; 1547 if (data_line_idx != last_data_line_idx) { 1548 let y1 = YBEGIN + LINE_SPACING * (i); 1549 let totalSeconds = totalSecondsPerLine[data_line_idx]; 1550 if (totalSeconds < 1) { 1551 timeDesc = (totalSeconds * 1000.0).toFixed(toFixedPrecision) + 'ms'; 1552 } else if (totalSeconds != undefined) { 1553 timeDesc = totalSeconds.toFixed(toFixedPrecision) + 's'; 1554 } else { 1555 timeDesc = "???" 1556 } 1557 1558 const n0 = numVisibleRequestsPerLine[data_line_idx]; 1559 const n1 = numFailedRequestsPerLine[data_line_idx]; 1560 let txt = ''; 1561 if (n1 > 0) { 1562 txt = '' + n0 + '+' + n1 + ' / ' + timeDesc; 1563 } else { 1564 txt = '' + n0 + ' / ' + timeDesc; 1565 } 1566 1567 const tw = ctx.measureText(txt).width; 1568 const PAD = 8; 1569 1570 ctx.fillStyle = '#000'; 1571 ctx.fillText(txt, 3, y1); 1572 totalOccs += numVisibleRequestsPerLine[data_line_idx]; 1573 totalSecs += totalSeconds; 1574 } 1575 last_data_line_idx = data_line_idx; 1576 } 1577 1578 // This does not get displayed correctly, so disabling for now 1579 //timeDesc = ''; 1580 //if (totalSecs < 1) { 1581 // timeDesc = '' + (totalSecs * 1000).toFixed(toFixedPrecision) + 'ms'; 1582 //} else { 1583 // timeDesc = '' + totalSecs.toFixed(toFixedPrecision) + 's'; 1584 //} 1585 1586 //ctx.fillText('Sum:', 3, y + 2 * LINE_SPACING); 1587 //ctx.fillText('' + totalOccs + ' / ' + timeDesc, 3, y + 3 * LINE_SPACING); 1588 1589 // Update highlighted requests 1590 if (this.IsHighlightDirty) { 1591 this.HighlightedRequests = currHighlightedReqs; 1592 this.IsHighlightDirty = false; 1593 1594 // Todo: This callback will be different for the DBus pane 1595 OnHighlightedChanged(HighlightedRequests); 1596 } 1597 1598 // Render highlight statistics 1599 if (this.IsHighlighted()) { 1600 ctx.fillStyle = 'rgba(128,128,255,0.5)'; 1601 let x0 = MapXCoord( 1602 t0, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 1603 this.UpperBoundTime); 1604 let x1 = MapXCoord( 1605 t1, LEFT_MARGIN, RIGHT_MARGIN, this.LowerBoundTime, 1606 this.UpperBoundTime); 1607 ctx.fillRect(x0, 0, x1 - x0, height); 1608 1609 let label0 = '' + t0.toFixed(toFixedPrecision) + 's'; 1610 let label1 = '' + t1.toFixed(toFixedPrecision) + 's'; 1611 let width0 = ctx.measureText(label0).width; 1612 let width1 = ctx.measureText(label1).width; 1613 let dispWidth = x1 - x0; 1614 // Draw time marks outside or inside? 1615 ctx.fillStyle = '#0000FF'; 1616 ctx.textBaseline = 'top'; 1617 if (dispWidth > width0 + width1) { 1618 ctx.textAlign = 'left'; 1619 ctx.fillText(label0, x0, LINE_SPACING + TEXT_Y0); 1620 ctx.textAlign = 'right'; 1621 ctx.fillText(label1, x1, LINE_SPACING + TEXT_Y0); 1622 } else { 1623 ctx.textAlign = 'right'; 1624 ctx.fillText(label0, x0, LINE_SPACING + TEXT_Y0); 1625 ctx.textAlign = 'left'; 1626 ctx.fillText(label1, x1, LINE_SPACING + TEXT_Y0); 1627 } 1628 1629 // This was calculated earlier 1630 ctx.textAlign = 'center'; 1631 label1 = 'Duration: ' + (t1 - t0).toFixed(toFixedPrecision) + 's'; 1632 ctx.fillText(label1, (x0 + x1) / 2, height - LINE_SPACING * 2); 1633 } 1634 1635 // Hovering cursor 1636 // Only draw when the mouse is not over any hotizontal scroll bar 1637 let should_hide_cursor = false; 1638 1639 if (this.MouseState.hoveredSide == "top_horizontal_scrollbar" || 1640 this.MouseState.hoveredSide == "bottom_horizontal_scrollbar") { 1641 should_hide_cursor = true; 1642 } 1643 this.linked_views.forEach((v) => { 1644 if (v.MouseState.hoveredSide == "top_horizontal_scrollbar" || 1645 v.MouseState.hoveredSide == "bottom_horizontal_scrollbar") { 1646 should_hide_cursor = true; 1647 } 1648 }) 1649 1650 if (this.MouseState.hovered == true && 1651 this.MouseState.hoveredSide == undefined && 1652 should_hide_cursor == false) { 1653 ctx.beginPath(); 1654 ctx.strokeStyle = '#0000FF'; 1655 ctx.lineWidth = 1; 1656 if (this.IsHighlighted()) { 1657 ctx.moveTo(this.MouseState.x, 0); 1658 ctx.lineTo(this.MouseState.x, height); 1659 } else { 1660 ctx.moveTo(this.MouseState.x, LINE_SPACING * 2); 1661 ctx.lineTo(this.MouseState.x, height - LINE_SPACING * 2); 1662 } 1663 ctx.stroke(); 1664 1665 if (this.IsHighlighted() == false) { 1666 let dispWidth = this.MouseState.x - LEFT_MARGIN; 1667 let label = '' + 1668 this.MouseXToTimestamp(this.MouseState.x) 1669 .toFixed(toFixedPrecision) + 1670 's'; 1671 let width0 = ctx.measureText(label).width; 1672 ctx.fillStyle = '#0000FF'; 1673 ctx.textBaseline = 'bottom'; 1674 ctx.textAlign = 'center'; 1675 ctx.fillText(label, this.MouseState.x, height - LINE_SPACING); 1676 ctx.textBaseline = 'top'; 1677 ctx.fillText(label, this.MouseState.x, LINE_SPACING + TEXT_Y0); 1678 } 1679 } 1680 1681 // Tooltip box next to hovered entry 1682 if (theHoveredReq !== undefined) { 1683 this.RenderToolTip( 1684 ctx, theHoveredReq, theHoveredInterval, toFixedPrecision, height); 1685 } 1686 } // End IsCanvasDirty 1687 } 1688 1689 // Returns list of highlighted messages. 1690 // Format: [ Title, [Message] ] 1691 HighlightedMessages() { 1692 let ret = []; 1693 if (this.HighlightedRegion.t0 == -999 || this.HighlightedRegion.t1 == -999) { return ret; } 1694 const lb = Math.min(this.HighlightedRegion.t0, this.HighlightedRegion.t1); 1695 const ub = Math.max(this.HighlightedRegion.t0, this.HighlightedRegion.t1); 1696 for (let i=0; i<this.Titles.length; i++) { 1697 const title = this.Titles[i]; 1698 if (title.header == true) continue; // Do not include headers. TODO: Allow rectangular selection 1699 1700 const line = [ title.title, [] ]; 1701 const interval_idx = title.intervals_idxes[0]; 1702 const intervals_i = this.Intervals[interval_idx]; 1703 for (let j=0; j<intervals_i.length; j++) { 1704 const m = intervals_i[j]; 1705 if (!(m[0] > ub || m[1] < lb)) { 1706 line[1].push(m); 1707 } 1708 } 1709 if (line[1].length > 0) { 1710 ret.push(line); 1711 } 1712 } 1713 return ret; 1714 } 1715}; 1716 1717// The extended classes have their own way of drawing popups for hovered entries 1718class IPMITimelineView extends TimelineView { 1719 RenderToolTip( 1720 ctx, theHoveredReq, theHoveredInterval, toFixedPrecision, height) { 1721 if (theHoveredReq == undefined) { 1722 return; 1723 } 1724 const PAD = 2, DELTA_Y = 14; 1725 1726 let labels = []; 1727 let netFn = theHoveredReq[0]; 1728 let cmd = theHoveredReq[1]; 1729 let t0 = theHoveredInterval[0]; 1730 let t1 = theHoveredInterval[1]; 1731 1732 labels.push('Netfn and CMD : (' + netFn + ', ' + cmd + ')'); 1733 let key = netFn + ', ' + cmd; 1734 1735 if (NetFnCmdToDescription[key] != undefined) { 1736 labels.push('Description : ' + NetFnCmdToDescription[key]); 1737 } 1738 1739 if (theHoveredReq.offset != undefined) { 1740 labels.push('Offset : ' + theHoveredReq.offset); 1741 } 1742 1743 let req = theHoveredReq[4]; 1744 labels.push('Request Data : ' + req.length + ' bytes'); 1745 if (req.length > 0) { 1746 labels.push('Hex : ' + ToHexString(req, '', ' ')); 1747 labels.push('ASCII : ' + ToASCIIString(req)); 1748 } 1749 let resp = theHoveredReq[5]; 1750 labels.push('Response Data : ' + theHoveredReq[5].length + ' bytes'); 1751 if (resp.length > 0) { 1752 labels.push('Hex : ' + ToHexString(resp, '', ' ')); 1753 labels.push('ASCII : ' + ToASCIIString(resp)); 1754 } 1755 labels.push('Start : ' + t0.toFixed(toFixedPrecision) + 's'); 1756 labels.push('End : ' + t1.toFixed(toFixedPrecision) + 's'); 1757 labels.push('Duration : ' + ((t1 - t0) * 1000).toFixed(3) + 'ms'); 1758 1759 1760 let w = 1, h = LINE_SPACING * labels.length + 2 * PAD; 1761 for (let i = 0; i < labels.length; i++) { 1762 w = Math.max(w, ctx.measureText(labels[i]).width); 1763 } 1764 let dy = this.MouseState.y + DELTA_Y; 1765 if (dy + h > height) { 1766 dy = height - h; 1767 } 1768 let dx = this.MouseState.x; 1769 if (RIGHT_MARGIN - dx < w) dx -= (w + 2 * PAD); 1770 1771 ctx.fillStyle = 'rgba(0,0,0,0.5)'; 1772 ctx.fillRect(dx, dy, w + 2 * PAD, h); 1773 1774 ctx.textAlign = 'left'; 1775 ctx.textBaseline = 'middle'; 1776 ctx.fillStyle = '#FFFFFF'; 1777 for (let i = 0; i < labels.length; i++) { 1778 ctx.fillText( 1779 labels[i], dx + PAD, dy + PAD + i * LINE_SPACING + LINE_SPACING / 2); 1780 } 1781 } 1782}; 1783 1784class DBusTimelineView extends TimelineView { 1785 RenderToolTip( 1786 ctx, theHoveredReq, theHoveredInterval, toFixedPrecision, height) { 1787 if (theHoveredReq == undefined) { 1788 return; 1789 } 1790 const PAD = 2, DELTA_Y = 14; 1791 1792 let labels = []; 1793 let msg_type = theHoveredReq[0]; 1794 let serial = theHoveredReq[2]; 1795 let sender = theHoveredReq[3]; 1796 let destination = theHoveredReq[4]; 1797 let path = theHoveredReq[5]; 1798 let iface = theHoveredReq[6]; 1799 let member = theHoveredReq[7]; 1800 1801 let t0 = theHoveredInterval[0]; 1802 let t1 = theHoveredInterval[1]; 1803 1804 labels.push('Message type: ' + msg_type); 1805 labels.push('Serial : ' + serial); 1806 labels.push('Sender : ' + sender); 1807 labels.push('Destination : ' + destination); 1808 labels.push('Path : ' + path); 1809 labels.push('Interface : ' + iface); 1810 labels.push('Member : ' + member); 1811 1812 let w = 1, h = LINE_SPACING * labels.length + 2 * PAD; 1813 for (let i = 0; i < labels.length; i++) { 1814 w = Math.max(w, ctx.measureText(labels[i]).width); 1815 } 1816 let dy = this.MouseState.y + DELTA_Y; 1817 if (dy + h > height) { 1818 dy = height - h; 1819 } 1820 let dx = this.MouseState.x; 1821 if (RIGHT_MARGIN - dx < w) dx -= (w + 2 * PAD); 1822 1823 ctx.fillStyle = 'rgba(0,0,0,0.5)'; 1824 ctx.fillRect(dx, dy, w + 2 * PAD, h); 1825 1826 ctx.textAlign = 'left'; 1827 ctx.textBaseline = 'middle'; 1828 ctx.fillStyle = '#FFFFFF'; 1829 for (let i = 0; i < labels.length; i++) { 1830 ctx.fillText( 1831 labels[i], dx + PAD, dy + PAD + i * LINE_SPACING + LINE_SPACING / 2); 1832 } 1833 } 1834}; 1835 1836class BoostASIOHandlerTimelineView extends TimelineView { 1837 RenderToolTip( 1838 ctx, theHoveredReq, theHoveredInterval, toFixedPrecision, height) { 1839 if (theHoveredReq == undefined) { 1840 return; 1841 } 1842 const PAD = 2, DELTA_Y = 14; 1843 1844 let labels = []; 1845 let create_time = theHoveredReq[2]; 1846 let enter_time = theHoveredReq[3]; 1847 let exit_time = theHoveredReq[4]; 1848 let desc = theHoveredReq[5]; 1849 1850 let t0 = theHoveredInterval[0]; 1851 let t1 = theHoveredInterval[1]; 1852 1853 labels.push('Creation time: ' + create_time); 1854 labels.push('Entry time : ' + enter_time); 1855 labels.push('Exit time : ' + exit_time); 1856 labels.push('Creation->Entry : ' + (enter_time - create_time)); 1857 labels.push('Entry->Exit : ' + (exit_time - enter_time)); 1858 labels.push('Description : ' + desc); 1859 1860 let w = 1, h = LINE_SPACING * labels.length + 2 * PAD; 1861 for (let i = 0; i < labels.length; i++) { 1862 w = Math.max(w, ctx.measureText(labels[i]).width); 1863 } 1864 let dy = this.MouseState.y + DELTA_Y; 1865 if (dy + h > height) { 1866 dy = height - h; 1867 } 1868 let dx = this.MouseState.x; 1869 if (RIGHT_MARGIN - dx < w) dx -= (w + 2 * PAD); 1870 1871 ctx.fillStyle = 'rgba(0,0,0,0.5)'; 1872 ctx.fillRect(dx, dy, w + 2 * PAD, h); 1873 1874 ctx.textAlign = 'left'; 1875 ctx.textBaseline = 'middle'; 1876 ctx.fillStyle = '#FFFFFF'; 1877 for (let i = 0; i < labels.length; i++) { 1878 ctx.fillText( 1879 labels[i], dx + PAD, dy + PAD + i * LINE_SPACING + LINE_SPACING / 2); 1880 } 1881 } 1882} 1883