1// 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 16const {dialog} = require('electron'); 17const {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, 578 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 } 584}; 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; 591 if (v.MouseState.pressed == true && 592 v.MouseState.hoveredSide == 'timeline') { // Update highlighted area 593 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 601 if (u.MouseState.pressed == true && 602 u.MouseState.hoveredSide == 'timeline') { // Update highlighted area 603 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} 734