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