blob: fd7ec03856ec19e82e4f72b8bf3c04a691065a12 [file] [log] [blame]
Sui Chenb65280f2020-06-30 18:14:03 -07001// This file is about the layout (Preproess() and Group()) of the IPMI time line view.
2// The layout happens according to the following sequence that is very similar to how
3// the layout is done for DBus messages:
4//
5// 1. User clicks any of the checkboxes for the grouping fields (NetFN, CMD)
6// 2. OnGroupByConditionChanged() is called
7// 3. OnGroupByConditionChanged() calls PreProcess() and Group()
8// 4. PreProcess() takes the IPMI messages extracted from the DBus capture
9// (g_ipmi_parsed_entries), and determines the start time.
10// 5. Group() takes the IPMI messages, and the list of keys, and groups the messages
11// by the keys. The output is picked up by GenerateTimeLine(), which writes the
12// timeline data into the Intervals and Titles arrays. The draw loop immediately
13// picks up the updated Intervals and Titles arrays and draws on the canvas
14// accordingly.
15
Sui Chenb53fa1b2022-05-26 00:16:42 -070016const {dialog} = require('electron');
Sui Chenb65280f2020-06-30 18:14:03 -070017const {fs} = require('file-system');
18const {util} = require('util');
19const {exec} = require('child_process');
20
21// Main view objects
22var ipmi_timeline_view = new IPMITimelineView();
23ipmi_timeline_view.IsTimeDistributionEnabled = true;
24
25var btn_start_capture = document.getElementById('btn_start_capture');
26var select_capture_mode = document.getElementById('select_capture_mode');
27var capture_info = document.getElementById('capture_info');
28
29var radio_open_file = document.getElementById('radio_open_file');
30var radio_capture = document.getElementById('radio_capture');
31var title_open_file = document.getElementById('title_open_file');
32var title_capture = document.getElementById('title_capture');
33
34// Set up Electron-related stuff here; Electron does not allow inlining button
35// events
36document.getElementById('c1').addEventListener(
37 'click', OnGroupByConditionChanged); // NetFN
38document.getElementById('c2').addEventListener(
39 'click', OnGroupByConditionChanged); // CMD
40
41// Zoom in button
42document.getElementById('btn_zoom_in').addEventListener('click', function() {
43 ipmi_timeline_view.BeginZoomAnimation(0.5);
44 boost_asio_handler_timeline_view.BeginZoomAnimation(0.5);
45});
46
47// Zoom out button
48document.getElementById('btn_zoom_out').addEventListener('click', function() {
49 ipmi_timeline_view.BeginZoomAnimation(-1);
50 boost_asio_handler_timeline_view.BeginZoomAnimation(-1);
51});
52
53// Pan left button
54document.getElementById('btn_pan_left').addEventListener('click', function() {
55 ipmi_timeline_view.BeginPanScreenAnimaton(-0.5);
56 boost_asio_handler_timeline_view.BeginPanScreenAnimaton(-0.5);
57});
58
59// Pan right button
60document.getElementById('btn_pan_right').addEventListener('click', function() {
61 ipmi_timeline_view.BeginPanScreenAnimaton(0.5);
62 boost_asio_handler_timeline_view.BeginPanScreenAnimaton(0.5);
63});
64
65// Reset zoom button
66document.getElementById('btn_zoom_reset').addEventListener('click', function() {
67 ipmi_timeline_view.BeginSetBoundaryAnimation(
68 RANGE_LEFT_INIT, RANGE_RIGHT_INIT)
69 dbus_timeline_view.BeginSetBoundaryAnimation(
70 RANGE_LEFT_INIT, RANGE_RIGHT_INIT)
71 boost_asio_handler_timeline_view.BeginSetBoundaryAnimation(
72 RANGE_LEFT_INIT, RANGE_RIGHT_INIT)
73})
74
75// Generate replay
76document.getElementById('gen_replay_ipmitool1')
77 .addEventListener('click', function() {
78 GenerateIPMIToolIndividualCommandReplay(HighlightedRequests);
79 });
80document.getElementById('gen_replay_ipmitool2')
81 .addEventListener('click', function() {
82 GenerateIPMIToolExecListReplay(HighlightedRequests);
83 });
84document.getElementById('gen_replay_ipmid_legacy')
85 .addEventListener('click', function() {
86 GenerateBusctlReplayLegacyInterface(HighlightedRequests);
87 });
88document.getElementById('gen_replay_ipmid_new')
89 .addEventListener('click', function() {
90 GenerateBusctlReplayNewInterface(HighlightedRequests);
91 });
92document.getElementById('btn_start_capture')
93 .addEventListener('click', function() {
94 let h = document.getElementById('text_hostname').value;
95 g_capture_state = 'started';
96 StartCapture(h);
97 });
98
99// For capture mode
100document.getElementById('btn_stop_capture')
101 .addEventListener('click', function() {
102 StopCapture();
103 });
104document.getElementById('select_capture_mode')
105 .addEventListener('click', OnCaptureModeChanged);
106radio_open_file.addEventListener('click', OnAppModeChanged);
107radio_capture.addEventListener('click', OnAppModeChanged);
108
109radio_open_file.click();
110
111// App mode: open file or capture
112function OnAppModeChanged() {
113 title_open_file.style.display = 'none';
114 title_capture.style.display = 'none';
115 if (radio_open_file.checked) {
116 title_open_file.style.display = 'block';
117 }
118 if (radio_capture.checked) {
119 title_capture.style.display = 'block';
120 }
121}
122
123// Capture mode: Live capture or staged capture
124function OnCaptureModeChanged() {
125 let x = select_capture_mode;
126 let i = capture_info;
127 let desc = '';
128 switch (x.value) {
129 case 'live':
130 desc = 'Live: read BMC\'s dbus-monitor console output directly';
131 g_capture_mode = 'live';
132 break;
133 case 'staged':
134 desc =
135 'Staged, IPMI only: Store BMC\'s dbus-monitor output in a file and transfer back for display';
136 g_capture_mode = 'staged';
137 break;
138 case 'staged2':
139 desc =
140 'Staged, DBus + IPMI: Store BMC\'s busctl output in a file and transfer back for display';
141 g_capture_mode = 'staged2';
142 break;
143 }
144 i.textContent = desc;
145}
146
147// Data
148var HistoryHistogram = [];
149// var Data_IPMI = []
150
151// =====================
152
153let Intervals = [];
154let Titles = [];
155let HighlightedRequests = [];
156let GroupBy = [];
157let GroupByStr = '';
158
159// (NetFn, Cmd) -> [ Bucket Indexes ]
160// Normalized (0~1) bucket index for the currently highlighted IPMI requests
161let IpmiVizHistHighlighted = {};
162let HistogramThresholds = {};
163
164function IsIntersected(i0, i1) {
165 return (!((i0[1] < i1[0]) || (i0[0] > i1[1])));
166}
167
168function IsIntersectedPixelCoords(i0, i1) {
169 if (i0[1] == undefined || isNaN(i0[1])) {
170 return (Math.abs(i0[0] - i1[0]) < 5);
171 } else {
172 return (IsIntersected(i0, i1));
173 }
174}
175
176var NetFnCmdToDescription = {
177 '6, 1': 'App-GetDeviceId',
178 '6, 3': 'App-WarmReset',
179 '10, 64': 'Storage-GetSelInfo',
180 '10, 35': 'Storage-GetSdr',
181 '4, 32': 'Sensor-GetDeviceSDRInfo',
182 '4, 34': 'Sensor-ReserveDeviceSDRRepo',
183 '4, 47': 'Sensor-GetSensorType',
184 '10, 34': 'Storage-ReserveSdrRepository',
185 '46, 50': 'OEM Extension',
186 '4, 39': 'Sensor-GetSensorThresholds',
187 '4, 45': 'Sensor-GetSensorReading',
188 '10, 67': 'Storage-GetSelEntry',
189 '58, 196': 'IBM_OEM',
190 '10, 32': 'Storage-GetSdrRepositoryInfo',
191 '4, 33': 'Sensor-GetDeviceSDR',
192 '6, 54': 'App-Get BT Interface Capabilities',
193 '10, 17': 'Storage-ReadFruData',
194 '10, 16': 'Storage-GetFruInventoryAreaInfo',
195 '4, 2': 'Sensor-PlatformEvent',
196 '4, 48': 'Sensor-SetSensor',
197 '6, 34': 'App-ResetWatchdogTimer'
198};
199
200const CANVAS_H = document.getElementById('my_canvas_ipmi').height;
201const CANVAS_W = document.getElementById('my_canvas_ipmi').width;
202
203var LowerBoundTime = RANGE_LEFT_INIT;
204var UpperBoundTime = RANGE_RIGHT_INIT;
205var LastTimeLowerBound;
206var LastTimeUpperBound;
207// Dirty flags for determining when to redraw the canvas
208let IsCanvasDirty = true;
209let IsHighlightDirty = false;
210// Animating left and right boundaries
211let IsAnimating = false;
212let LowerBoundTimeTarget = LowerBoundTime;
213let UpperBoundTimeTarget = UpperBoundTime;
214// For keyboard interaction: arrow keys and Shift
215let CurrDeltaX = 0; // Proportion of Canvas to scroll per frame.
216let CurrDeltaZoom = 0; // Delta zoom per frame.
217let CurrShiftFlag = false; // Whether the Shift key is depressed
218
219// TODO: these variables are shared across all views but are now in ipmi_timeline_vis.js, need to move to some other location some time
220const LEFT_MARGIN = 640
221const RIGHT_MARGIN = 1390;
222const LINE_HEIGHT = 15;
223const LINE_SPACING = 17;
224const YBEGIN = 22 + LINE_SPACING;
225const TOP_HORIZONTAL_SCROLLBAR_HEIGHT = YBEGIN - LINE_HEIGHT / 2; // ybegin is the center of the 1st line of the text so need to minus line_height/2
226const BOTTOM_HORIZONTAL_SCROLLBAR_HEIGHT = LINE_HEIGHT;
227const TEXT_Y0 = 3;
228const HISTOGRAM_W = 100, HISTOGRAM_H = LINE_SPACING;
229const HISTOGRAM_X = 270;
230// If some request's time is beyond the right tail, it's considered "too long"
231// If some request's time is below the left tail it's considered "good"
232// const HISTOGRAM_LEFT_TAIL_WIDTH = 0.05, HISTOGRAM_RIGHT_TAIL_WIDTH = 0.05;
233// temporarily disabled for now
234const HISTOGRAM_LEFT_TAIL_WIDTH = -1, HISTOGRAM_RIGHT_TAIL_WIDTH = -1;
235const SCROLL_BAR_WIDTH = 16;
236
237let IpmiVizHistogramImageData = {}; // Image data for rendered histogram
238
239// Input is the data that's completed layout
240// is_free_x: Should each histogram has its own X range or not
241// num_buckets: # of buckets for histograms
242// theta: top and bottom portion to cut
243function ComputeHistogram(num_buckets = 30, is_free_x = true) {
244 let global_lb = Infinity, global_ub = -Infinity;
245 IpmiVizHistogramImageData = {};
246 // Global minimal and maximal values
247 for (let i = 0; i < Intervals.length; i++) {
248 let interval = Intervals[i];
249 let l = Math.min.apply(Math, interval.map(function(x) {
250 return x[1] - x[0];
251 }));
252 let u = Math.max.apply(Math, interval.map(function(x) {
253 return x[1] - x[0];
254 }));
255 global_lb = Math.min(l, global_lb);
256 global_ub = Math.max(u, global_ub);
257 }
258
259 HistoryHistogram = [];
260 for (let i = 0; i < Intervals.length; i++) {
261 let interval = Intervals[i];
262 let lb = global_lb, ub = global_ub;
263 if (is_free_x == true) {
264 lb = Math.min.apply(Math, interval.map(function(x) {
265 return x[1] - x[0];
266 }));
267 ub = Math.max.apply(Math, interval.map(function(x) {
268 return x[1] - x[0];
269 }));
270 }
271 const EPS = 1e-2;
272 if (lb == ub) ub = lb + EPS;
273 let line = [lb * 1000000, ub * 1000000]; // to usec
274 let buckets = [];
275 for (let j = 0; j < num_buckets; j++) buckets.push(0);
276 for (let j = 0; j < interval.length; j++) {
277 let t = interval[j][1] - interval[j][0];
278 let bucket_idx = parseInt(t / ((ub - lb) / num_buckets));
279 buckets[bucket_idx]++;
280 }
281 line.push(buckets);
282 HistoryHistogram[Titles[i].title] = line;
283 }
284}
285
286function Preprocess(data) {
287 preprocessed = [];
288 let StartingUsec_IPMI;
289
290 if (g_StartingSec == undefined) {
291 StartingUsec_IPMI = undefined;
292 } else {
293 StartingUsec_IPMI = g_StartingSec * 1000000;
294 }
295
296 for (let i = 0; i < data.length; i++) {
297 let entry = data[i].slice();
298 let lb = entry[2], ub = entry[3];
299
300 // Only when IPMI view is present (i.e. no DBus pcap is loaded)
301 if (i == 0 && StartingUsec_IPMI == undefined) {
302 StartingUsec_IPMI = lb;
303 }
304
305 entry[2] = lb - StartingUsec_IPMI;
306 entry[3] = ub - StartingUsec_IPMI;
307 preprocessed.push(entry);
308 }
309 return preprocessed;
310}
311
312let SHOW_BLOB_DETAILS = true;
313function Group(data, groupBy) {
314 let grouped = {};
315
316 // If has netfn and cmd: use "NetFN, CMD" as key
317 // Otherwise, use "NetFN" as key
318 // This distinction is made if the user chooses to label operation on each
319 // blob individually
320
321 // Key: blob name
322 // Value: the commands that operate on the particular blob
323 let sid2blobid = {}
324
325 for (let n = 0; n < data.length; n++) {
326 const p = data[n];
327 const netfn = p[0], cmd = p[1], req = p[4], res = p[5];
328 if (netfn == 46 && cmd == 128) {
329 const oen = req[0] + req[1] * 256 + req[2] * 65536;
330 if (oen == 0xc2cf) { // Blob operations
331 const blobcmd =
332 req[3]; // Refer to https://github.com/openbmc/phosphor-ipmi-blobs
333
334 // The IPMI blob commands are visible on DBus, another WIP command-line tool that
335 // utilizes this fact to show information about blobs can be found here:
336 // https://gerrit.openbmc-project.xyz/c/openbmc/openbmc-tools/+/41451
337
338 let sid, blobid;
339
340 // layout of req
341 // 0 1 2 3 4 5 6 7 8 9 10 ...
342 // CF C2 00 CMD [CRC ] [ other stuff ]
343
344 // layout of res
345 // 0 1 2 3 4 5 6 7 8 ...
346 // CF C2 00 [CRC ] [other stuff]
347
348 // Determining blob id and session ID
349 switch (blobcmd) {
350 case 3:
351 case 4:
352 case 5:
353 case 6:
354 case 9:
355 case 10: {
356 const sid = req[6] + req[7] * 256;
357 blobid = sid2blobid[sid];
358 if (blobid != undefined) {
359 p.key = blobid;
360 }
361 break;
362 }
363 case 7:
364 case 8: {
365 blobid = '';
366 for (let i = 6; i < req.length; i++) {
367 blobid += String.fromCharCode(req[i]);
368 }
369 break;
370 }
371 }
372
373 switch (blobcmd) {
374 case 2: { // open
375 blobid = '';
376 for (let i = 8; i < req.length; i++) {
377 if (req[i] == 0)
378 break;
379 else
380 blobid += String.fromCharCode(req[i]);
381 }
382 p.key = blobid;
383 sid = res[5] + res[6] * 256; // session_id
384 sid2blobid[sid] = blobid;
385 break;
386 }
387 case 3: { // Read
388
389 break;
390 }
391 case 4: { // Write
392 const offset =
393 req[8] + req[9] * 256 + req[10] * 65536 + req[11] * 16777216;
394 p.offset = offset;
395 break;
396 }
397 case 5: { // Commit
398 break;
399 }
400 case 6: { // Close
401 break;
402 }
403 }
404 }
405 }
406 }
407
408 const idxes = {'NetFN': 0, 'CMD': 1};
409
410 //
411 for (let n = 0; n < data.length; n++) {
412 const p = data[n];
413 let key = '';
414 if (p.key != undefined)
415 key = p.key;
416 else if (p[0] != '' && p[1] != '') {
417 for (let i = 0; i < groupBy.length; i++) {
418 if (i > 0) {
419 key += ', ';
420 }
421 key += p[idxes[groupBy[i]]];
422 }
423 }
424
425 if (grouped[key] == undefined) {
426 grouped[key] = [];
427 }
428 grouped[key].push(p);
429 }
430
431 return grouped;
432}
433
434function GenerateTimeLine(grouped) {
435 const keys = Object.keys(grouped);
436 let sortedKeys = keys.slice();
437 // If NetFN and CMD are both selected, sort by NetFN then CMD
438 // In this case, all "keys" are string-encoded integer pairs
439 if (keys.length > 0 && ipmi_timeline_view.GroupBy.length == 2) {
440 sortedKeys = sortedKeys.sort(function(a, b) {
441 a = a.split(',');
442 b = b.split(',');
443 if (a.length == 2 && b.length == 2) {
444 let aa = parseInt(a[0]) * 256 + parseInt(a[1]);
445 let bb = parseInt(b[0]) * 256 + parseInt(b[1]);
446 return aa < bb ? -1 : (aa > bb ? 1 : 0);
447 } else {
448 return a < b ? -1 : (a > b ? 1 : 0);
449 }
450 });
451 }
452
453 Intervals = [];
454 Titles = [];
455 for (let i = 0; i < sortedKeys.length; i++) {
456 Titles.push({"header":false, "title":sortedKeys[i], "intervals_idxes":[i]});
457 line = [];
458 for (let j = 0; j < grouped[sortedKeys[i]].length; j++) {
459 let entry = grouped[sortedKeys[i]][j];
460 // Lower bound, Upper bound, and a reference to the original request
461 line.push([
462 parseFloat(entry[2]) / 1000000, parseFloat(entry[3]) / 1000000, entry,
463 'ok', 0
464 ]);
465 }
466 Intervals.push(line);
467 }
468
469 ipmi_timeline_view.Intervals = Intervals.slice();
470 ipmi_timeline_view.Titles = Titles.slice();
471 ipmi_timeline_view.LayoutForOverlappingIntervals();
472}
473
474function OnGroupByConditionChanged() {
475 const tags = ['c1', 'c2'];
476 const v = ipmi_timeline_view;
477 v.GroupBy = [];
478 v.GroupByStr = '';
479 for (let i = 0; i < tags.length; i++) {
480 let cb = document.getElementById(tags[i]);
481 if (cb.checked) {
482 v.GroupBy.push(cb.value);
483 if (v.GroupByStr.length > 0) {
484 v.GroupByStr += ', ';
485 }
486 v.GroupByStr += cb.value;
487 }
488 }
489 let preproc = Preprocess(Data_IPMI);
490 grouped = Group(preproc, v.GroupBy);
491 GenerateTimeLine(grouped);
492
493 IsCanvasDirty = true;
494 ipmi_timeline_view.IsCanvasDirty = true;
495}
496
497function MapXCoord(x, left_margin, right_margin, rl, rr) {
498 let ret = left_margin + (x - rl) / (rr - rl) * (right_margin - left_margin);
499 if (ret < left_margin) {
500 ret = left_margin;
501 } else if (ret > right_margin) {
502 ret = right_margin;
503 }
504 return ret;
505}
506
507function draw_timeline(ctx) {
508 ipmi_timeline_view.Render(ctx);
509}
510
511
512window.addEventListener('keydown', function() {
513 if (event.keyCode == 37) { // Left Arrow
514 ipmi_timeline_view.CurrDeltaX = -0.004;
515 dbus_timeline_view.CurrDeltaX = -0.004;
516 } else if (event.keyCode == 39) { // Right arrow
517 ipmi_timeline_view.CurrDeltaX = 0.004;
518 dbus_timeline_view.CurrDeltaX = 0.004;
519 } else if (event.keyCode == 16) { // Shift
520 ipmi_timeline_view.CurrShiftFlag = true;
521 dbus_timeline_view.CurrShiftFlag = true;
522 } else if (event.keyCode == 38) { // Up arrow
523 ipmi_timeline_view.CurrDeltaZoom = 0.01;
524 dbus_timeline_view.CurrDeltaZoom = 0.01;
525 } else if (event.keyCode == 40) { // Down arrow
526 ipmi_timeline_view.CurrDeltaZoom = -0.01;
527 dbus_timeline_view.CurrDeltaZoom = -0.01;
528 }
529});
530
531window.addEventListener('keyup', function() {
532 if (event.keyCode == 37 || event.keyCode == 39) {
533 ipmi_timeline_view.CurrDeltaX = 0;
534 dbus_timeline_view.CurrDeltaX = 0;
535 } else if (event.keyCode == 16) {
536 ipmi_timeline_view.CurrShiftFlag = false;
537 dbus_timeline_view.CurrShiftFlag = false;
538 } else if (event.keyCode == 38 || event.keyCode == 40) {
539 ipmi_timeline_view.CurrDeltaZoom = 0;
540 dbus_timeline_view.CurrDeltaZoom = 0;
541 }
542});
543
544function MouseXToTimestamp(x) {
545 let ret = (x - LEFT_MARGIN) / (RIGHT_MARGIN - LEFT_MARGIN) *
546 (UpperBoundTime - LowerBoundTime) +
547 LowerBoundTime;
548 ret = Math.max(ret, LowerBoundTime);
549 ret = Math.min(ret, UpperBoundTime);
550 return ret;
551}
552
553let HighlightedRegion = {t0: -999, t1: -999};
554
555function IsHighlighted() {
556 return (HighlightedRegion.t0 != -999 && HighlightedRegion.t1 != -999);
557}
558
559function Unhighlight() {
560 HighlightedRegion.t0 = -999;
561 HighlightedRegion.t1 = -999;
562}
563
564function UnhighlightIfEmpty() {
565 if (HighlightedRegion.t0 == HighlightedRegion.t1) {
566 Unhighlight();
567 return true;
568 }
569 return false;
570}
571
572let MouseState = {
573 hovered: true,
574 pressed: false,
575 x: 0,
576 y: 0,
577 hoveredVisibleLineIndex: -999,
Sui Chen27cf9332021-11-03 16:20:28 -0700578 hoveredSide: undefined,
579 IsHoveredOverHorizontalScrollbar: function() {
580 if (this.hoveredSide == "top_horizontal_scrollbar") return true;
581 else if (this.hoveredSide == "bottom_horizontal_scrollbar") return true;
582 else return false;
583 }
Sui Chenb65280f2020-06-30 18:14:03 -0700584};
585let Canvas = document.getElementById('my_canvas_ipmi');
586
587Canvas.onmousemove = function(event) {
588 const v = ipmi_timeline_view;
589 v.MouseState.x = event.pageX - this.offsetLeft;
590 v.MouseState.y = event.pageY - this.offsetTop;
Sui Chen27cf9332021-11-03 16:20:28 -0700591 if (v.MouseState.pressed == true &&
592 v.MouseState.hoveredSide == 'timeline') { // Update highlighted area
Sui Chenb65280f2020-06-30 18:14:03 -0700593 v.HighlightedRegion.t1 = v.MouseXToTimestamp(v.MouseState.x);
594 }
595 v.OnMouseMove();
596 v.IsCanvasDirty = true;
597
598 v.linked_views.forEach(function(u) {
599 u.MouseState.x = event.pageX - Canvas.offsetLeft;
600 u.MouseState.y = 0; // Do not highlight any entry
Sui Chen27cf9332021-11-03 16:20:28 -0700601 if (u.MouseState.pressed == true &&
602 u.MouseState.hoveredSide == 'timeline') { // Update highlighted area
Sui Chenb65280f2020-06-30 18:14:03 -0700603 u.HighlightedRegion.t1 = u.MouseXToTimestamp(u.MouseState.x);
604 }
605 u.OnMouseMove();
606 u.IsCanvasDirty = true;
607 });
608};
609
610Canvas.onmouseover = function() {
611 ipmi_timeline_view.OnMouseMove();
612};
613
614Canvas.onmouseleave = function() {
615 ipmi_timeline_view.OnMouseLeave();
616};
617
618Canvas.onmousedown = function(event) {
619 if (event.button == 0) { // Left mouse button
620 ipmi_timeline_view.OnMouseDown();
621 }
622};
623
624Canvas.onmouseup = function(event) {
625 if (event.button == 0) {
626 ipmi_timeline_view.OnMouseUp();
627 // page-specific, not view-specific
628 let hint = document.getElementById('highlight_hint');
629 if (ipmi_timeline_view.UnhighlightIfEmpty()) {
630 hint.style.display = 'none';
631 } else {
632 hint.style.display = 'block';
633 }
634 }
635};
636
637Canvas.onwheel = function(event) {
638 ipmi_timeline_view.OnMouseWheel(event);
639};
640
641// This function is not specific to TimelineView so putting it here
642function OnHighlightedChanged(reqs) {
643 let x = document.getElementById('ipmi_replay');
644 let i = document.getElementById('ipmi_replay_output');
645 let cnt = document.getElementById('highlight_count');
646 cnt.innerHTML = '' + reqs.length;
647 i.style.display = 'none';
648 if (reqs.length > 0) {
649 x.style.display = 'block';
650 } else
651 x.style.display = 'none';
652 let o = document.getElementById('ipmi_replay_output');
653 o.style.display = 'none';
654 o.textContent = '';
655}
656
657function ToHexString(bytes, prefix, sep) {
658 let ret = '';
659 for (let i = 0; i < bytes.length; i++) {
660 if (i > 0) {
661 ret += sep;
662 }
663 ret += prefix + bytes[i].toString(16);
664 }
665 return ret;
666}
667
668function ToASCIIString(bytes) {
669 ret = '';
670 for (let i = 0; i < bytes.length; i++) {
671 ret = ret + String.fromCharCode(bytes[i]);
672 }
673 return ret;
674}
675
676function ShowReplayOutputs(x, ncols) {
677 let o = document.getElementById('ipmi_replay_output');
678 o.cols = ncols;
679 o.style.display = 'block';
680 o.textContent = x;
681}
682
683function GenerateIPMIToolIndividualCommandReplay(reqs) {
684 let x = '';
685 for (let i = 0; i < reqs.length; i++) {
686 let req = reqs[i];
687 // [0]: NetFN, [1]: cmd, [4]: payload
688 // NetFN and cmd are DECIMAL while payload is HEXADECIMAL.
689 x = x + 'ipmitool raw ' + req[0] + ' ' + req[1] + ' ' +
690 ToHexString(req[4], '0x', ' ') + '\n';
691 }
692 ShowReplayOutputs(x, 80);
693}
694
695function GenerateIPMIToolExecListReplay(reqs) {
696 console.log(reqs.length);
697 let x = '';
698 for (let i = 0; i < reqs.length; i++) {
699 let req = reqs[i];
700 x = x + 'raw ' +
701 ToHexString([req[0]].concat([req[1]]).concat(req[4]), '0x', ' ') + '\n';
702 }
703 ShowReplayOutputs(x, 80);
704}
705
706function GenerateBusctlReplayLegacyInterface(reqs) {
707 console.log(reqs.length);
708 let serial = 0;
709 let x = '';
710 for (let i = 0; i < reqs.length; i++) {
711 let req = reqs[i];
712 x = x +
713 'busctl --system emit /org/openbmc/HostIpmi/1 org.openbmc.HostIpmi ReceivedMessage yyyyay ';
714 x = x + serial + ' ' + req[0] + ' 0 ' + req[1] + ' ' + req[4].length + ' ' +
715 ToHexString(req[4], '0x', ' ') + '\n';
716 serial = (serial + 1) % 256;
717 }
718 ShowReplayOutputs(x, 120);
719}
720
721function GenerateBusctlReplayNewInterface(reqs) {
722 console.log(reqs.length);
723 let x = '';
724 for (let i = 0; i < reqs.length; i++) {
725 let req = reqs[i];
726 x = x +
727 'busctl --system call xyz.openbmc_project.Ipmi.Host /xyz/openbmc_project/Ipmi xyz.openbmc_project.Ipmi.Server execute yyyaya{sv} ';
728 x = x + req[0] + ' 0 ' + req[1] + ' ' + req[4].length + ' ' +
729 ToHexString(req[4], '0x', ' ');
730 +' 0\n';
731 }
732 ShowReplayOutputs(x, 150);
733}