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