| // This file is about the layout (Preproess() and Group()) of the IPMI time line view. |
| // The layout happens according to the following sequence that is very similar to how |
| // the layout is done for DBus messages: |
| // |
| // 1. User clicks any of the checkboxes for the grouping fields (NetFN, CMD) |
| // 2. OnGroupByConditionChanged() is called |
| // 3. OnGroupByConditionChanged() calls PreProcess() and Group() |
| // 4. PreProcess() takes the IPMI messages extracted from the DBus capture |
| // (g_ipmi_parsed_entries), and determines the start time. |
| // 5. Group() takes the IPMI messages, and the list of keys, and groups the messages |
| // by the keys. The output is picked up by GenerateTimeLine(), which writes the |
| // timeline data into the Intervals and Titles arrays. The draw loop immediately |
| // picks up the updated Intervals and Titles arrays and draws on the canvas |
| // accordingly. |
| |
| const {dialog} = require('electron').remote; |
| const {fs} = require('file-system'); |
| const {util} = require('util'); |
| const {exec} = require('child_process'); |
| |
| // Main view objects |
| var ipmi_timeline_view = new IPMITimelineView(); |
| ipmi_timeline_view.IsTimeDistributionEnabled = true; |
| |
| var btn_start_capture = document.getElementById('btn_start_capture'); |
| var select_capture_mode = document.getElementById('select_capture_mode'); |
| var capture_info = document.getElementById('capture_info'); |
| |
| var radio_open_file = document.getElementById('radio_open_file'); |
| var radio_capture = document.getElementById('radio_capture'); |
| var title_open_file = document.getElementById('title_open_file'); |
| var title_capture = document.getElementById('title_capture'); |
| |
| // Set up Electron-related stuff here; Electron does not allow inlining button |
| // events |
| document.getElementById('c1').addEventListener( |
| 'click', OnGroupByConditionChanged); // NetFN |
| document.getElementById('c2').addEventListener( |
| 'click', OnGroupByConditionChanged); // CMD |
| |
| // Zoom in button |
| document.getElementById('btn_zoom_in').addEventListener('click', function() { |
| ipmi_timeline_view.BeginZoomAnimation(0.5); |
| boost_asio_handler_timeline_view.BeginZoomAnimation(0.5); |
| }); |
| |
| // Zoom out button |
| document.getElementById('btn_zoom_out').addEventListener('click', function() { |
| ipmi_timeline_view.BeginZoomAnimation(-1); |
| boost_asio_handler_timeline_view.BeginZoomAnimation(-1); |
| }); |
| |
| // Pan left button |
| document.getElementById('btn_pan_left').addEventListener('click', function() { |
| ipmi_timeline_view.BeginPanScreenAnimaton(-0.5); |
| boost_asio_handler_timeline_view.BeginPanScreenAnimaton(-0.5); |
| }); |
| |
| // Pan right button |
| document.getElementById('btn_pan_right').addEventListener('click', function() { |
| ipmi_timeline_view.BeginPanScreenAnimaton(0.5); |
| boost_asio_handler_timeline_view.BeginPanScreenAnimaton(0.5); |
| }); |
| |
| // Reset zoom button |
| document.getElementById('btn_zoom_reset').addEventListener('click', function() { |
| ipmi_timeline_view.BeginSetBoundaryAnimation( |
| RANGE_LEFT_INIT, RANGE_RIGHT_INIT) |
| dbus_timeline_view.BeginSetBoundaryAnimation( |
| RANGE_LEFT_INIT, RANGE_RIGHT_INIT) |
| boost_asio_handler_timeline_view.BeginSetBoundaryAnimation( |
| RANGE_LEFT_INIT, RANGE_RIGHT_INIT) |
| }) |
| |
| // Generate replay |
| document.getElementById('gen_replay_ipmitool1') |
| .addEventListener('click', function() { |
| GenerateIPMIToolIndividualCommandReplay(HighlightedRequests); |
| }); |
| document.getElementById('gen_replay_ipmitool2') |
| .addEventListener('click', function() { |
| GenerateIPMIToolExecListReplay(HighlightedRequests); |
| }); |
| document.getElementById('gen_replay_ipmid_legacy') |
| .addEventListener('click', function() { |
| GenerateBusctlReplayLegacyInterface(HighlightedRequests); |
| }); |
| document.getElementById('gen_replay_ipmid_new') |
| .addEventListener('click', function() { |
| GenerateBusctlReplayNewInterface(HighlightedRequests); |
| }); |
| document.getElementById('btn_start_capture') |
| .addEventListener('click', function() { |
| let h = document.getElementById('text_hostname').value; |
| g_capture_state = 'started'; |
| StartCapture(h); |
| }); |
| |
| // For capture mode |
| document.getElementById('btn_stop_capture') |
| .addEventListener('click', function() { |
| StopCapture(); |
| }); |
| document.getElementById('select_capture_mode') |
| .addEventListener('click', OnCaptureModeChanged); |
| radio_open_file.addEventListener('click', OnAppModeChanged); |
| radio_capture.addEventListener('click', OnAppModeChanged); |
| |
| radio_open_file.click(); |
| |
| // App mode: open file or capture |
| function OnAppModeChanged() { |
| title_open_file.style.display = 'none'; |
| title_capture.style.display = 'none'; |
| if (radio_open_file.checked) { |
| title_open_file.style.display = 'block'; |
| } |
| if (radio_capture.checked) { |
| title_capture.style.display = 'block'; |
| } |
| } |
| |
| // Capture mode: Live capture or staged capture |
| function OnCaptureModeChanged() { |
| let x = select_capture_mode; |
| let i = capture_info; |
| let desc = ''; |
| switch (x.value) { |
| case 'live': |
| desc = 'Live: read BMC\'s dbus-monitor console output directly'; |
| g_capture_mode = 'live'; |
| break; |
| case 'staged': |
| desc = |
| 'Staged, IPMI only: Store BMC\'s dbus-monitor output in a file and transfer back for display'; |
| g_capture_mode = 'staged'; |
| break; |
| case 'staged2': |
| desc = |
| 'Staged, DBus + IPMI: Store BMC\'s busctl output in a file and transfer back for display'; |
| g_capture_mode = 'staged2'; |
| break; |
| } |
| i.textContent = desc; |
| } |
| |
| // Data |
| var HistoryHistogram = []; |
| // var Data_IPMI = [] |
| |
| // ===================== |
| |
| let Intervals = []; |
| let Titles = []; |
| let HighlightedRequests = []; |
| let GroupBy = []; |
| let GroupByStr = ''; |
| |
| // (NetFn, Cmd) -> [ Bucket Indexes ] |
| // Normalized (0~1) bucket index for the currently highlighted IPMI requests |
| let IpmiVizHistHighlighted = {}; |
| let HistogramThresholds = {}; |
| |
| function IsIntersected(i0, i1) { |
| return (!((i0[1] < i1[0]) || (i0[0] > i1[1]))); |
| } |
| |
| function IsIntersectedPixelCoords(i0, i1) { |
| if (i0[1] == undefined || isNaN(i0[1])) { |
| return (Math.abs(i0[0] - i1[0]) < 5); |
| } else { |
| return (IsIntersected(i0, i1)); |
| } |
| } |
| |
| var NetFnCmdToDescription = { |
| '6, 1': 'App-GetDeviceId', |
| '6, 3': 'App-WarmReset', |
| '10, 64': 'Storage-GetSelInfo', |
| '10, 35': 'Storage-GetSdr', |
| '4, 32': 'Sensor-GetDeviceSDRInfo', |
| '4, 34': 'Sensor-ReserveDeviceSDRRepo', |
| '4, 47': 'Sensor-GetSensorType', |
| '10, 34': 'Storage-ReserveSdrRepository', |
| '46, 50': 'OEM Extension', |
| '4, 39': 'Sensor-GetSensorThresholds', |
| '4, 45': 'Sensor-GetSensorReading', |
| '10, 67': 'Storage-GetSelEntry', |
| '58, 196': 'IBM_OEM', |
| '10, 32': 'Storage-GetSdrRepositoryInfo', |
| '4, 33': 'Sensor-GetDeviceSDR', |
| '6, 54': 'App-Get BT Interface Capabilities', |
| '10, 17': 'Storage-ReadFruData', |
| '10, 16': 'Storage-GetFruInventoryAreaInfo', |
| '4, 2': 'Sensor-PlatformEvent', |
| '4, 48': 'Sensor-SetSensor', |
| '6, 34': 'App-ResetWatchdogTimer' |
| }; |
| |
| const CANVAS_H = document.getElementById('my_canvas_ipmi').height; |
| const CANVAS_W = document.getElementById('my_canvas_ipmi').width; |
| |
| var LowerBoundTime = RANGE_LEFT_INIT; |
| var UpperBoundTime = RANGE_RIGHT_INIT; |
| var LastTimeLowerBound; |
| var LastTimeUpperBound; |
| // Dirty flags for determining when to redraw the canvas |
| let IsCanvasDirty = true; |
| let IsHighlightDirty = false; |
| // Animating left and right boundaries |
| let IsAnimating = false; |
| let LowerBoundTimeTarget = LowerBoundTime; |
| let UpperBoundTimeTarget = UpperBoundTime; |
| // For keyboard interaction: arrow keys and Shift |
| let CurrDeltaX = 0; // Proportion of Canvas to scroll per frame. |
| let CurrDeltaZoom = 0; // Delta zoom per frame. |
| let CurrShiftFlag = false; // Whether the Shift key is depressed |
| |
| // 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 |
| const LEFT_MARGIN = 640 |
| const RIGHT_MARGIN = 1390; |
| const LINE_HEIGHT = 15; |
| const LINE_SPACING = 17; |
| const YBEGIN = 22 + LINE_SPACING; |
| const 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 |
| const BOTTOM_HORIZONTAL_SCROLLBAR_HEIGHT = LINE_HEIGHT; |
| const TEXT_Y0 = 3; |
| const HISTOGRAM_W = 100, HISTOGRAM_H = LINE_SPACING; |
| const HISTOGRAM_X = 270; |
| // If some request's time is beyond the right tail, it's considered "too long" |
| // If some request's time is below the left tail it's considered "good" |
| // const HISTOGRAM_LEFT_TAIL_WIDTH = 0.05, HISTOGRAM_RIGHT_TAIL_WIDTH = 0.05; |
| // temporarily disabled for now |
| const HISTOGRAM_LEFT_TAIL_WIDTH = -1, HISTOGRAM_RIGHT_TAIL_WIDTH = -1; |
| const SCROLL_BAR_WIDTH = 16; |
| |
| let IpmiVizHistogramImageData = {}; // Image data for rendered histogram |
| |
| // Input is the data that's completed layout |
| // is_free_x: Should each histogram has its own X range or not |
| // num_buckets: # of buckets for histograms |
| // theta: top and bottom portion to cut |
| function ComputeHistogram(num_buckets = 30, is_free_x = true) { |
| let global_lb = Infinity, global_ub = -Infinity; |
| IpmiVizHistogramImageData = {}; |
| // Global minimal and maximal values |
| for (let i = 0; i < Intervals.length; i++) { |
| let interval = Intervals[i]; |
| let l = Math.min.apply(Math, interval.map(function(x) { |
| return x[1] - x[0]; |
| })); |
| let u = Math.max.apply(Math, interval.map(function(x) { |
| return x[1] - x[0]; |
| })); |
| global_lb = Math.min(l, global_lb); |
| global_ub = Math.max(u, global_ub); |
| } |
| |
| HistoryHistogram = []; |
| for (let i = 0; i < Intervals.length; i++) { |
| let interval = Intervals[i]; |
| let lb = global_lb, ub = global_ub; |
| if (is_free_x == true) { |
| lb = Math.min.apply(Math, interval.map(function(x) { |
| return x[1] - x[0]; |
| })); |
| ub = Math.max.apply(Math, interval.map(function(x) { |
| return x[1] - x[0]; |
| })); |
| } |
| const EPS = 1e-2; |
| if (lb == ub) ub = lb + EPS; |
| let line = [lb * 1000000, ub * 1000000]; // to usec |
| let buckets = []; |
| for (let j = 0; j < num_buckets; j++) buckets.push(0); |
| for (let j = 0; j < interval.length; j++) { |
| let t = interval[j][1] - interval[j][0]; |
| let bucket_idx = parseInt(t / ((ub - lb) / num_buckets)); |
| buckets[bucket_idx]++; |
| } |
| line.push(buckets); |
| HistoryHistogram[Titles[i].title] = line; |
| } |
| } |
| |
| function Preprocess(data) { |
| preprocessed = []; |
| let StartingUsec_IPMI; |
| |
| if (g_StartingSec == undefined) { |
| StartingUsec_IPMI = undefined; |
| } else { |
| StartingUsec_IPMI = g_StartingSec * 1000000; |
| } |
| |
| for (let i = 0; i < data.length; i++) { |
| let entry = data[i].slice(); |
| let lb = entry[2], ub = entry[3]; |
| |
| // Only when IPMI view is present (i.e. no DBus pcap is loaded) |
| if (i == 0 && StartingUsec_IPMI == undefined) { |
| StartingUsec_IPMI = lb; |
| } |
| |
| entry[2] = lb - StartingUsec_IPMI; |
| entry[3] = ub - StartingUsec_IPMI; |
| preprocessed.push(entry); |
| } |
| return preprocessed; |
| } |
| |
| let SHOW_BLOB_DETAILS = true; |
| function Group(data, groupBy) { |
| let grouped = {}; |
| |
| // If has netfn and cmd: use "NetFN, CMD" as key |
| // Otherwise, use "NetFN" as key |
| // This distinction is made if the user chooses to label operation on each |
| // blob individually |
| |
| // Key: blob name |
| // Value: the commands that operate on the particular blob |
| let sid2blobid = {} |
| |
| for (let n = 0; n < data.length; n++) { |
| const p = data[n]; |
| const netfn = p[0], cmd = p[1], req = p[4], res = p[5]; |
| if (netfn == 46 && cmd == 128) { |
| const oen = req[0] + req[1] * 256 + req[2] * 65536; |
| if (oen == 0xc2cf) { // Blob operations |
| const blobcmd = |
| req[3]; // Refer to https://github.com/openbmc/phosphor-ipmi-blobs |
| |
| // The IPMI blob commands are visible on DBus, another WIP command-line tool that |
| // utilizes this fact to show information about blobs can be found here: |
| // https://gerrit.openbmc-project.xyz/c/openbmc/openbmc-tools/+/41451 |
| |
| let sid, blobid; |
| |
| // layout of req |
| // 0 1 2 3 4 5 6 7 8 9 10 ... |
| // CF C2 00 CMD [CRC ] [ other stuff ] |
| |
| // layout of res |
| // 0 1 2 3 4 5 6 7 8 ... |
| // CF C2 00 [CRC ] [other stuff] |
| |
| // Determining blob id and session ID |
| switch (blobcmd) { |
| case 3: |
| case 4: |
| case 5: |
| case 6: |
| case 9: |
| case 10: { |
| const sid = req[6] + req[7] * 256; |
| blobid = sid2blobid[sid]; |
| if (blobid != undefined) { |
| p.key = blobid; |
| } |
| break; |
| } |
| case 7: |
| case 8: { |
| blobid = ''; |
| for (let i = 6; i < req.length; i++) { |
| blobid += String.fromCharCode(req[i]); |
| } |
| break; |
| } |
| } |
| |
| switch (blobcmd) { |
| case 2: { // open |
| blobid = ''; |
| for (let i = 8; i < req.length; i++) { |
| if (req[i] == 0) |
| break; |
| else |
| blobid += String.fromCharCode(req[i]); |
| } |
| p.key = blobid; |
| sid = res[5] + res[6] * 256; // session_id |
| sid2blobid[sid] = blobid; |
| break; |
| } |
| case 3: { // Read |
| |
| break; |
| } |
| case 4: { // Write |
| const offset = |
| req[8] + req[9] * 256 + req[10] * 65536 + req[11] * 16777216; |
| p.offset = offset; |
| break; |
| } |
| case 5: { // Commit |
| break; |
| } |
| case 6: { // Close |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| const idxes = {'NetFN': 0, 'CMD': 1}; |
| |
| // |
| for (let n = 0; n < data.length; n++) { |
| const p = data[n]; |
| let key = ''; |
| if (p.key != undefined) |
| key = p.key; |
| else if (p[0] != '' && p[1] != '') { |
| for (let i = 0; i < groupBy.length; i++) { |
| if (i > 0) { |
| key += ', '; |
| } |
| key += p[idxes[groupBy[i]]]; |
| } |
| } |
| |
| if (grouped[key] == undefined) { |
| grouped[key] = []; |
| } |
| grouped[key].push(p); |
| } |
| |
| return grouped; |
| } |
| |
| function GenerateTimeLine(grouped) { |
| const keys = Object.keys(grouped); |
| let sortedKeys = keys.slice(); |
| // If NetFN and CMD are both selected, sort by NetFN then CMD |
| // In this case, all "keys" are string-encoded integer pairs |
| if (keys.length > 0 && ipmi_timeline_view.GroupBy.length == 2) { |
| sortedKeys = sortedKeys.sort(function(a, b) { |
| a = a.split(','); |
| b = b.split(','); |
| if (a.length == 2 && b.length == 2) { |
| let aa = parseInt(a[0]) * 256 + parseInt(a[1]); |
| let bb = parseInt(b[0]) * 256 + parseInt(b[1]); |
| return aa < bb ? -1 : (aa > bb ? 1 : 0); |
| } else { |
| return a < b ? -1 : (a > b ? 1 : 0); |
| } |
| }); |
| } |
| |
| Intervals = []; |
| Titles = []; |
| for (let i = 0; i < sortedKeys.length; i++) { |
| Titles.push({"header":false, "title":sortedKeys[i], "intervals_idxes":[i]}); |
| line = []; |
| for (let j = 0; j < grouped[sortedKeys[i]].length; j++) { |
| let entry = grouped[sortedKeys[i]][j]; |
| // Lower bound, Upper bound, and a reference to the original request |
| line.push([ |
| parseFloat(entry[2]) / 1000000, parseFloat(entry[3]) / 1000000, entry, |
| 'ok', 0 |
| ]); |
| } |
| Intervals.push(line); |
| } |
| |
| ipmi_timeline_view.Intervals = Intervals.slice(); |
| ipmi_timeline_view.Titles = Titles.slice(); |
| ipmi_timeline_view.LayoutForOverlappingIntervals(); |
| } |
| |
| function OnGroupByConditionChanged() { |
| const tags = ['c1', 'c2']; |
| const v = ipmi_timeline_view; |
| v.GroupBy = []; |
| v.GroupByStr = ''; |
| for (let i = 0; i < tags.length; i++) { |
| let cb = document.getElementById(tags[i]); |
| if (cb.checked) { |
| v.GroupBy.push(cb.value); |
| if (v.GroupByStr.length > 0) { |
| v.GroupByStr += ', '; |
| } |
| v.GroupByStr += cb.value; |
| } |
| } |
| let preproc = Preprocess(Data_IPMI); |
| grouped = Group(preproc, v.GroupBy); |
| GenerateTimeLine(grouped); |
| |
| IsCanvasDirty = true; |
| ipmi_timeline_view.IsCanvasDirty = true; |
| } |
| |
| function MapXCoord(x, left_margin, right_margin, rl, rr) { |
| let ret = left_margin + (x - rl) / (rr - rl) * (right_margin - left_margin); |
| if (ret < left_margin) { |
| ret = left_margin; |
| } else if (ret > right_margin) { |
| ret = right_margin; |
| } |
| return ret; |
| } |
| |
| function draw_timeline(ctx) { |
| ipmi_timeline_view.Render(ctx); |
| } |
| |
| |
| window.addEventListener('keydown', function() { |
| if (event.keyCode == 37) { // Left Arrow |
| ipmi_timeline_view.CurrDeltaX = -0.004; |
| dbus_timeline_view.CurrDeltaX = -0.004; |
| } else if (event.keyCode == 39) { // Right arrow |
| ipmi_timeline_view.CurrDeltaX = 0.004; |
| dbus_timeline_view.CurrDeltaX = 0.004; |
| } else if (event.keyCode == 16) { // Shift |
| ipmi_timeline_view.CurrShiftFlag = true; |
| dbus_timeline_view.CurrShiftFlag = true; |
| } else if (event.keyCode == 38) { // Up arrow |
| ipmi_timeline_view.CurrDeltaZoom = 0.01; |
| dbus_timeline_view.CurrDeltaZoom = 0.01; |
| } else if (event.keyCode == 40) { // Down arrow |
| ipmi_timeline_view.CurrDeltaZoom = -0.01; |
| dbus_timeline_view.CurrDeltaZoom = -0.01; |
| } |
| }); |
| |
| window.addEventListener('keyup', function() { |
| if (event.keyCode == 37 || event.keyCode == 39) { |
| ipmi_timeline_view.CurrDeltaX = 0; |
| dbus_timeline_view.CurrDeltaX = 0; |
| } else if (event.keyCode == 16) { |
| ipmi_timeline_view.CurrShiftFlag = false; |
| dbus_timeline_view.CurrShiftFlag = false; |
| } else if (event.keyCode == 38 || event.keyCode == 40) { |
| ipmi_timeline_view.CurrDeltaZoom = 0; |
| dbus_timeline_view.CurrDeltaZoom = 0; |
| } |
| }); |
| |
| function MouseXToTimestamp(x) { |
| let ret = (x - LEFT_MARGIN) / (RIGHT_MARGIN - LEFT_MARGIN) * |
| (UpperBoundTime - LowerBoundTime) + |
| LowerBoundTime; |
| ret = Math.max(ret, LowerBoundTime); |
| ret = Math.min(ret, UpperBoundTime); |
| return ret; |
| } |
| |
| let HighlightedRegion = {t0: -999, t1: -999}; |
| |
| function IsHighlighted() { |
| return (HighlightedRegion.t0 != -999 && HighlightedRegion.t1 != -999); |
| } |
| |
| function Unhighlight() { |
| HighlightedRegion.t0 = -999; |
| HighlightedRegion.t1 = -999; |
| } |
| |
| function UnhighlightIfEmpty() { |
| if (HighlightedRegion.t0 == HighlightedRegion.t1) { |
| Unhighlight(); |
| return true; |
| } |
| return false; |
| } |
| |
| let MouseState = { |
| hovered: true, |
| pressed: false, |
| x: 0, |
| y: 0, |
| hoveredVisibleLineIndex: -999, |
| hoveredSide: undefined, |
| IsHoveredOverHorizontalScrollbar: function() { |
| if (this.hoveredSide == "top_horizontal_scrollbar") return true; |
| else if (this.hoveredSide == "bottom_horizontal_scrollbar") return true; |
| else return false; |
| } |
| }; |
| let Canvas = document.getElementById('my_canvas_ipmi'); |
| |
| Canvas.onmousemove = function(event) { |
| const v = ipmi_timeline_view; |
| v.MouseState.x = event.pageX - this.offsetLeft; |
| v.MouseState.y = event.pageY - this.offsetTop; |
| if (v.MouseState.pressed == true && |
| v.MouseState.hoveredSide == 'timeline') { // Update highlighted area |
| v.HighlightedRegion.t1 = v.MouseXToTimestamp(v.MouseState.x); |
| } |
| v.OnMouseMove(); |
| v.IsCanvasDirty = true; |
| |
| v.linked_views.forEach(function(u) { |
| u.MouseState.x = event.pageX - Canvas.offsetLeft; |
| u.MouseState.y = 0; // Do not highlight any entry |
| if (u.MouseState.pressed == true && |
| u.MouseState.hoveredSide == 'timeline') { // Update highlighted area |
| u.HighlightedRegion.t1 = u.MouseXToTimestamp(u.MouseState.x); |
| } |
| u.OnMouseMove(); |
| u.IsCanvasDirty = true; |
| }); |
| }; |
| |
| Canvas.onmouseover = function() { |
| ipmi_timeline_view.OnMouseMove(); |
| }; |
| |
| Canvas.onmouseleave = function() { |
| ipmi_timeline_view.OnMouseLeave(); |
| }; |
| |
| Canvas.onmousedown = function(event) { |
| if (event.button == 0) { // Left mouse button |
| ipmi_timeline_view.OnMouseDown(); |
| } |
| }; |
| |
| Canvas.onmouseup = function(event) { |
| if (event.button == 0) { |
| ipmi_timeline_view.OnMouseUp(); |
| // page-specific, not view-specific |
| let hint = document.getElementById('highlight_hint'); |
| if (ipmi_timeline_view.UnhighlightIfEmpty()) { |
| hint.style.display = 'none'; |
| } else { |
| hint.style.display = 'block'; |
| } |
| } |
| }; |
| |
| Canvas.onwheel = function(event) { |
| ipmi_timeline_view.OnMouseWheel(event); |
| }; |
| |
| // This function is not specific to TimelineView so putting it here |
| function OnHighlightedChanged(reqs) { |
| let x = document.getElementById('ipmi_replay'); |
| let i = document.getElementById('ipmi_replay_output'); |
| let cnt = document.getElementById('highlight_count'); |
| cnt.innerHTML = '' + reqs.length; |
| i.style.display = 'none'; |
| if (reqs.length > 0) { |
| x.style.display = 'block'; |
| } else |
| x.style.display = 'none'; |
| let o = document.getElementById('ipmi_replay_output'); |
| o.style.display = 'none'; |
| o.textContent = ''; |
| } |
| |
| function ToHexString(bytes, prefix, sep) { |
| let ret = ''; |
| for (let i = 0; i < bytes.length; i++) { |
| if (i > 0) { |
| ret += sep; |
| } |
| ret += prefix + bytes[i].toString(16); |
| } |
| return ret; |
| } |
| |
| function ToASCIIString(bytes) { |
| ret = ''; |
| for (let i = 0; i < bytes.length; i++) { |
| ret = ret + String.fromCharCode(bytes[i]); |
| } |
| return ret; |
| } |
| |
| function ShowReplayOutputs(x, ncols) { |
| let o = document.getElementById('ipmi_replay_output'); |
| o.cols = ncols; |
| o.style.display = 'block'; |
| o.textContent = x; |
| } |
| |
| function GenerateIPMIToolIndividualCommandReplay(reqs) { |
| let x = ''; |
| for (let i = 0; i < reqs.length; i++) { |
| let req = reqs[i]; |
| // [0]: NetFN, [1]: cmd, [4]: payload |
| // NetFN and cmd are DECIMAL while payload is HEXADECIMAL. |
| x = x + 'ipmitool raw ' + req[0] + ' ' + req[1] + ' ' + |
| ToHexString(req[4], '0x', ' ') + '\n'; |
| } |
| ShowReplayOutputs(x, 80); |
| } |
| |
| function GenerateIPMIToolExecListReplay(reqs) { |
| console.log(reqs.length); |
| let x = ''; |
| for (let i = 0; i < reqs.length; i++) { |
| let req = reqs[i]; |
| x = x + 'raw ' + |
| ToHexString([req[0]].concat([req[1]]).concat(req[4]), '0x', ' ') + '\n'; |
| } |
| ShowReplayOutputs(x, 80); |
| } |
| |
| function GenerateBusctlReplayLegacyInterface(reqs) { |
| console.log(reqs.length); |
| let serial = 0; |
| let x = ''; |
| for (let i = 0; i < reqs.length; i++) { |
| let req = reqs[i]; |
| x = x + |
| 'busctl --system emit /org/openbmc/HostIpmi/1 org.openbmc.HostIpmi ReceivedMessage yyyyay '; |
| x = x + serial + ' ' + req[0] + ' 0 ' + req[1] + ' ' + req[4].length + ' ' + |
| ToHexString(req[4], '0x', ' ') + '\n'; |
| serial = (serial + 1) % 256; |
| } |
| ShowReplayOutputs(x, 120); |
| } |
| |
| function GenerateBusctlReplayNewInterface(reqs) { |
| console.log(reqs.length); |
| let x = ''; |
| for (let i = 0; i < reqs.length; i++) { |
| let req = reqs[i]; |
| x = x + |
| 'busctl --system call xyz.openbmc_project.Ipmi.Host /xyz/openbmc_project/Ipmi xyz.openbmc_project.Ipmi.Server execute yyyaya{sv} '; |
| x = x + req[0] + ' 0 ' + req[1] + ' ' + req[4].length + ' ' + |
| ToHexString(req[4], '0x', ' '); |
| +' 0\n'; |
| } |
| ShowReplayOutputs(x, 150); |
| } |