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