blob: 98b94a716506c9b6f26c1c98fc62dd26846eb82d [file] [log] [blame]
Sui Chenb65280f2020-06-30 18:14:03 -07001const { 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;
Sui Chen27cf9332021-11-03 16:20:28 -0700174 },
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;
Sui Chenb65280f2020-06-30 18:14:03 -0700179 }
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 }
Sui Chene8c12082022-03-06 09:53:14 -0800254 return undefined;
Sui Chenb65280f2020-06-30 18:14:03 -0700255 }
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;
Sui Chenc403b032022-03-06 18:03:12 -0800629
630 // If highlighted area changed, update the info panel
631 UpdateHighlightedMessagesInfoPanel();
Sui Chenb65280f2020-06-30 18:14:03 -0700632 }
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,
Sui Chene8c12082022-03-06 09:53:14 -0800686 vars,
687 is_in_viewport) {
Sui Chenb65280f2020-06-30 18:14:03 -0700688 // 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;
Sui Chenc403b032022-03-06 18:03:12 -0800710 vars.currHighlightedReqs.push(intervals_j[i][2]); // TODO: change the name to avoid confusion with HighlightedMessages
Sui Chenb65280f2020-06-30 18:14:03 -0700711 }
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
Sui Chene8c12082022-03-06 09:53:14 -0800739 if (isHighlighted && is_in_viewport) {
Sui Chenb65280f2020-06-30 18:14:03 -0700740 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)';
Sui Chene8c12082022-03-06 09:53:14 -0800758 if (is_in_viewport) ctx.fillRect(dx0, dy0, dw, dy1 - dy0);
Sui Chenb65280f2020-06-30 18:14:03 -0700759 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)) {
Sui Chene8c12082022-03-06 09:53:14 -0800790 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 }
Sui Chenb65280f2020-06-30 18:14:03 -0700799 }
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) {
Sui Chene8c12082022-03-06 09:53:14 -0800808 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 }
Sui Chenb65280f2020-06-30 18:14:03 -0700814 last_dx_begin = dx0;
815 }
816 } else {
Sui Chene8c12082022-03-06 09:53:14 -0800817 if (is_in_viewport) {
818 ctx.strokeRect(dx0, dy0, dw, dy1 - dy0);
819 }
Sui Chenb65280f2020-06-30 18:14:03 -0700820 }
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
Sui Chene8c12082022-03-06 09:53:14 -0800826 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();
Sui Chenb65280f2020-06-30 18:14:03 -0700835 }
Sui Chenb65280f2020-06-30 18:14:03 -0700836 }
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,
Sui Chene8c12082022-03-06 09:53:14 -0800882 vars, is_in_viewport) {
Sui Chenb65280f2020-06-30 18:14:03 -0700883
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;
Sui Chene8c12082022-03-06 09:53:14 -0800896 if (is_in_viewport) {
897 ctx.fillRect(0, dy-LINE_HEIGHT/2, x0, LINE_HEIGHT);
Sui Chenb65280f2020-06-30 18:14:03 -0700898
Sui Chene8c12082022-03-06 09:53:14 -0800899 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();
Sui Chenb65280f2020-06-30 18:14:03 -0700905
Sui Chene8c12082022-03-06 09:53:14 -0800906 ctx.beginPath();
907 ctx.lineWidth = 1.5;
908 ctx.moveTo(0, dy1);
909 ctx.lineTo(RIGHT_MARGIN, dy1);
910 ctx.stroke();
911 ctx.closePath();
Sui Chenb65280f2020-06-30 18:14:03 -0700912
Sui Chene8c12082022-03-06 09:53:14 -0800913 ctx.fillStyle = '#003';
914 ctx.textBaseline = 'center';
915 ctx.textAlign = 'right';
916 ctx.fillText(title_text, LEFT_MARGIN - LINE_HEIGHT, dy);
917 }
Sui Chenb65280f2020-06-30 18:14:03 -0700918
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;
Sui Chene8c12082022-03-06 09:53:14 -0800923 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();
Sui Chenb65280f2020-06-30 18:14:03 -0700933
Sui Chene8c12082022-03-06 09:53:14 -0800934 /*
935 ctx.beginPath();
936 ctx.moveTo(0, dy);
937 ctx.lineTo(RIGHT_MARGIN, dy);
938 ctx.stroke();
939 ctx.closePath();
940 */
Sui Chenb65280f2020-06-30 18:14:03 -0700941
Sui Chene8c12082022-03-06 09:53:14 -0800942 ctx.fillStyle = '#003';
943 ctx.textBaseline = 'center';
944 ctx.textAlign = 'right';
945 ctx.fillText(title_text, LEFT_MARGIN - LINE_HEIGHT, dy);
946 }
Sui Chenb65280f2020-06-30 18:14:03 -0700947 }
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);
Sui Chene8c12082022-03-06 09:53:14 -08001003 if (is_in_viewport) {
1004 ctx.fillRect(dxx0, dy0, dxx1-dxx0, LINE_HEIGHT);
1005 }
Sui Chenb65280f2020-06-30 18:14:03 -07001006 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
Sui Chene8c12082022-03-06 09:53:14 -08001254 // '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.
Sui Chenb65280f2020-06-30 18:14:03 -07001264 // A "visual row" might be one of:
1265 // 1. a "header" line
1266 // 2. an actual row of data (in the Intervals variable)
Sui Chene8c12082022-03-06 09:53:14 -08001267
1268 // 'j' needs to go PAST the viewport if the last row is overlapping and spans beyond the viewport.
Sui Chen3926ebd2022-05-19 00:17:05 -07001269 for (let j = j0; j < tvh; j++) {
Sui Chene8c12082022-03-06 09:53:14 -08001270
1271 if (j >= this.VisualLineStartIdx) { should_render = true; }
1272 if (y > height) { should_render = false; }
1273
Sui Chenb65280f2020-06-30 18:14:03 -07001274 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
Sui Chene8c12082022-03-06 09:53:14 -08001279 const should_render_title = (data_line_idx != last_data_line_idx) ||
1280 (j == this.VisualLineStartIdx); // The first visible line should always be drawn
Sui Chenb65280f2020-06-30 18:14:03 -07001281 last_data_line_idx = data_line_idx;
1282
Sui Chene8c12082022-03-06 09:53:14 -08001283 if (should_render_title && data_line_idx != -999 && should_render) { // Only draw line title and histogram per data line index not visual line index
Sui Chenb65280f2020-06-30 18:14:03 -07001284 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 }
Sui Chene8c12082022-03-06 09:53:14 -08001323 } else if (should_render_title && data_line_idx == -999) {
Sui Chenb65280f2020-06-30 18:14:03 -07001324 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,
Sui Chene8c12082022-03-06 09:53:14 -08001362 data_line_idx, visual_line_offset_within_data_line, isAggregateSelection, vars, should_render);
Sui Chenb65280f2020-06-30 18:14:03 -07001363 }
1364 } else {
1365 this.do_RenderHeader(ctx, this.Titles[data_line_idx],
1366 j, dy0, dy1,
Sui Chene8c12082022-03-06 09:53:14 -08001367 data_line_idx, visual_line_offset_within_data_line, isAggregateSelection, vars, should_render);
Sui Chenb65280f2020-06-30 18:14:03 -07001368 }
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 }
Sui Chenb65280f2020-06-30 18:14:03 -07001424
Sui Chene8c12082022-03-06 09:53:14 -08001425 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;
Sui Chenb65280f2020-06-30 18:14:03 -07001437
1438 title_end_idx = j;
Sui Chene8c12082022-03-06 09:53:14 -08001439
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 }
Sui Chenb65280f2020-06-30 18:14:03 -07001446 }
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';
Sui Chene8c12082022-03-06 09:53:14 -08001552 } else if (totalSeconds != undefined) {
Sui Chenb65280f2020-06-30 18:14:03 -07001553 timeDesc = totalSeconds.toFixed(toFixedPrecision) + 's';
Sui Chene8c12082022-03-06 09:53:14 -08001554 } else {
1555 timeDesc = "???"
Sui Chenb65280f2020-06-30 18:14:03 -07001556 }
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 }
Sui Chenc403b032022-03-06 18:03:12 -08001688
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 }
Sui Chenb65280f2020-06-30 18:14:03 -07001715};
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
Sui Chen1ee50072022-08-14 23:44:57 -07001812 let packet_idx = -1;
1813 if (msg_type == 'mc') {
1814 packet_idx = 10;
1815 } else if (msg_type == 'sig') {
1816 packet_idx = 9;
1817 }
1818
1819 if (packet_idx != -1) {
1820 let packet = theHoveredReq[packet_idx];
1821 if (packet.length >= 2) {
1822 const args = packet[1].length;
1823 if (args.length < 1) {
1824 labels.push("(no args)");
1825 } else {
1826 for (let i = 0; i < packet[1].length; i ++) {
1827 labels.push('args[' + i + "]: " + packet[1][i]);
1828 }
1829 }
1830 }
1831 }
1832
Sui Chenb65280f2020-06-30 18:14:03 -07001833 let w = 1, h = LINE_SPACING * labels.length + 2 * PAD;
1834 for (let i = 0; i < labels.length; i++) {
1835 w = Math.max(w, ctx.measureText(labels[i]).width);
1836 }
1837 let dy = this.MouseState.y + DELTA_Y;
1838 if (dy + h > height) {
1839 dy = height - h;
1840 }
1841 let dx = this.MouseState.x;
1842 if (RIGHT_MARGIN - dx < w) dx -= (w + 2 * PAD);
1843
1844 ctx.fillStyle = 'rgba(0,0,0,0.5)';
1845 ctx.fillRect(dx, dy, w + 2 * PAD, h);
1846
1847 ctx.textAlign = 'left';
1848 ctx.textBaseline = 'middle';
1849 ctx.fillStyle = '#FFFFFF';
1850 for (let i = 0; i < labels.length; i++) {
1851 ctx.fillText(
1852 labels[i], dx + PAD, dy + PAD + i * LINE_SPACING + LINE_SPACING / 2);
1853 }
1854 }
1855};
1856
1857class BoostASIOHandlerTimelineView extends TimelineView {
1858 RenderToolTip(
1859 ctx, theHoveredReq, theHoveredInterval, toFixedPrecision, height) {
1860 if (theHoveredReq == undefined) {
1861 return;
1862 }
1863 const PAD = 2, DELTA_Y = 14;
1864
1865 let labels = [];
1866 let create_time = theHoveredReq[2];
1867 let enter_time = theHoveredReq[3];
1868 let exit_time = theHoveredReq[4];
1869 let desc = theHoveredReq[5];
1870
1871 let t0 = theHoveredInterval[0];
1872 let t1 = theHoveredInterval[1];
1873
1874 labels.push('Creation time: ' + create_time);
1875 labels.push('Entry time : ' + enter_time);
1876 labels.push('Exit time : ' + exit_time);
1877 labels.push('Creation->Entry : ' + (enter_time - create_time));
1878 labels.push('Entry->Exit : ' + (exit_time - enter_time));
1879 labels.push('Description : ' + desc);
1880
1881 let w = 1, h = LINE_SPACING * labels.length + 2 * PAD;
1882 for (let i = 0; i < labels.length; i++) {
1883 w = Math.max(w, ctx.measureText(labels[i]).width);
1884 }
1885 let dy = this.MouseState.y + DELTA_Y;
1886 if (dy + h > height) {
1887 dy = height - h;
1888 }
1889 let dx = this.MouseState.x;
1890 if (RIGHT_MARGIN - dx < w) dx -= (w + 2 * PAD);
1891
1892 ctx.fillStyle = 'rgba(0,0,0,0.5)';
1893 ctx.fillRect(dx, dy, w + 2 * PAD, h);
1894
1895 ctx.textAlign = 'left';
1896 ctx.textBaseline = 'middle';
1897 ctx.fillStyle = '#FFFFFF';
1898 for (let i = 0; i < labels.length; i++) {
1899 ctx.fillText(
1900 labels[i], dx + PAD, dy + PAD + i * LINE_SPACING + LINE_SPACING / 2);
1901 }
1902 }
1903}