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