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