1// This file deals with preprocessing the parsed DBus timeline data.
2// Data and Timestamps are separate b/c dbus-pcap does not include
3// timestamps in JSON output so we need to export both formats
4// (JSON and text)
5var Data_DBus = [];
6var Timestamps_DBus = [];
7
8// Main view object
9var dbus_timeline_view = new DBusTimelineView();
10var sensors_timeline_view = new DBusTimelineView(); // Same DBusTimelineView type, just that it will have only sensor propertieschanged events
11
12// group-by condition changes
13{
14  const tags = [
15    'dbus_column1', 'dbus_column2', 'dbus_column3', 'dbus_column4',
16    'dbus_column5', 'dbus_column6', 'dbus_column7'
17  ];
18  for (let i = 0; i < 7; i++) {
19    document.getElementById(tags[i]).addEventListener(
20        'click', OnGroupByConditionChanged_DBus);
21  }
22}
23
24// Called from renderer.Render()
25function draw_timeline_sensors(ctx) {
26  sensors_timeline_view.Render(ctx);
27}
28
29// Called from renderer.Render()
30function draw_timeline_dbus(ctx) {
31  dbus_timeline_view.Render(ctx);
32}
33
34let Canvas_DBus = document.getElementById('my_canvas_dbus');
35
36const IDXES = {
37  'Type': 0,
38  'Timestamp': 1,
39  'Serial': 2,
40  'Sender': 3,
41  'Destination': 4,
42  'Path': 5,
43  'Interface': 6,
44  'Member': 7
45};
46
47// This "group" is based on the content of the DBus
48// It is independent of the "group_by" of the meta-data (sender/destination/
49// path/interface/member) of a DBus message
50//
51// Input is processed message and some basic statistics needed for categorizing
52//
53const DBusMessageContentKey = function(msg, cxn_occ) {
54  let ret = undefined;
55  const type = msg[IDXES["Type"]];
56  const dest = msg[IDXES["Destination"]];
57  const path = msg[IDXES["Path"]];
58  const iface = msg[IDXES["Interface"]];
59  const member = msg[IDXES["Member"]];
60  const sender = msg[IDXES["Sender"]];
61
62  if (sender == "s" || sender == "sss") {
63    console.log(msg)
64  }
65
66  if (type == "sig") {
67    if (path.indexOf("/xyz/openbmc_project/sensors/") != -1 &&
68        iface == "org.freedesktop.DBus.Properties" &&
69        member == "PropertiesChanged") {
70      ret = "Sensor PropertiesChanged Signals";
71    }
72  } else if (type == "mc") {
73    if (dest == "xyz.openbmc_project.Ipmi.Host" &&
74        path == "/xyz/openbmc_project/Ipmi" &&
75        iface == "xyz.openbmc_project.Ipmi.Server" &&
76        member == "execute") {
77      ret = "IPMI Daemon";
78    }
79  }
80
81  if (ret == undefined && cxn_occ[sender] <= 10) {
82    ret = "Total 10 messages or less"
83  }
84
85  if (ret == undefined && type == "mc") {
86    if (path.indexOf("/xyz/openbmc_project/sensors/") == 0 &&
87    iface == "org.freedesktop.DBus.Properties" &&
88    (member.startsWith("Get") || member.startsWith("Set"))) {
89      ret = "Sensor Get/Set";
90    }
91  }
92
93  if (ret == undefined) {
94    ret = "Uncategorized";
95  }
96
97  return ret;
98}
99
100function Group_DBus(preprocessed, group_by) {
101  let grouped = {};  // [content key][sort key] -> packet
102
103  let cxn_occ = {}; // How many times have a specific service appeared?
104  preprocessed.forEach((pp) => {
105    const cxn = pp[IDXES["Sender"]];
106    if (cxn_occ[cxn] == undefined) {
107      cxn_occ[cxn] = 0;
108    }
109    cxn_occ[cxn]++;
110  });
111
112  for (var n = 0; n < preprocessed.length; n++) {
113    var key = '';
114    for (var i = 0; i < group_by.length; i++) {
115      if (i > 0) key += ' ';
116      key += ('' + preprocessed[n][IDXES[group_by[i]]]);
117    }
118
119    // "Content Key" is displayed on the "Column Headers"
120    const content_group = DBusMessageContentKey(preprocessed[n], cxn_occ);
121
122    // Initialize the "Collapsed" array here
123    // TODO: this should ideally not be specific to the dbus_interface_view instance
124    if (dbus_timeline_view.HeaderCollapsed[content_group] == undefined) {
125      dbus_timeline_view.HeaderCollapsed[content_group] = false;  // Not collapsed by default
126    }
127
128    if (grouped[content_group] == undefined) {
129      grouped[content_group] = [];
130    }
131    let grouped1 = grouped[content_group];
132
133    if (grouped1[key] == undefined) grouped1[key] = [];
134    grouped1[key].push(preprocessed[n]);
135  }
136  return grouped;
137}
138
139function OnGroupByConditionChanged_DBus() {
140  var tags = [
141    'dbus_column1', 'dbus_column2', 'dbus_column3', 'dbus_column4',
142    'dbus_column5', 'dbus_column6', 'dbus_column7'
143  ];
144  const v = dbus_timeline_view;
145  v.GroupBy = [];
146  v.GroupByStr = '';
147  for (let i = 0; i < tags.length; i++) {
148    let cb = document.getElementById(tags[i]);
149    if (cb.checked) {
150      v.GroupBy.push(cb.value);
151      if (v.GroupByStr.length > 0) {
152        v.GroupByStr += ', ';
153      }
154      v.GroupByStr += cb.value;
155    }
156  }
157  let preproc = Preprocess_DBusPcap(
158      Data_DBus, Timestamps_DBus);  // should be from dbus_pcap
159  let grouped = Group_DBus(preproc, v.GroupBy);
160  GenerateTimeLine_DBus(grouped);
161  dbus_timeline_view.IsCanvasDirty = true;
162}
163
164// Todo: put g_StartingSec somewhere that's common between sensors and non-sensors
165function GenerateTimeLine_DBus(grouped) {
166  let intervals = [];
167  let titles = [];
168  g_StartingSec = undefined;
169
170  // First, turn "content keys" into headers in the flattened layout
171  const content_keys = Object.keys(grouped);
172
173  const keys = Object.keys(grouped);
174  let sortedKeys = keys.slice();
175
176  let interval_idx = 0;  // The overall index into the intervals array
177
178  for (let x=0; x<content_keys.length; x++) {
179    const content_key = content_keys[x];
180    // Per-content key
181    const grouped1 = grouped[content_key];
182    const keys1 = Object.keys(grouped1);
183
184    let the_header = { "header":true, "title":content_key, "intervals_idxes":[] };
185    titles.push(the_header);
186    // TODO: this currently depends on the dbus_timeline_view instance
187    const collapsed = dbus_timeline_view.HeaderCollapsed[content_key];
188
189    for (let i = 0; i < keys1.length; i++) {
190      // The Title array controls which lines are drawn. If we con't push the header
191      // it will not be drawn (thus giving a "collapsed" visual effect.)
192      if (!collapsed) {
193        titles.push({ "header":false, "title":keys1[i], "intervals_idxes":[interval_idx] });
194      }
195
196
197      line = [];
198      for (let j = 0; j < grouped1[keys1[i]].length; j++) {
199        let entry = grouped1[keys1[i]][j];
200        let t0 = parseFloat(entry[1]) / 1000.0;
201        let t1 = parseFloat(entry[8]) / 1000.0;
202
203        // Modify time shift delta if IPMI dataset is loaded first
204        if (g_StartingSec == undefined) {
205          g_StartingSec = t0;
206        }
207        g_StartingSec = Math.min(g_StartingSec, t0);
208        const outcome = entry[9];
209        line.push([t0, t1, entry, outcome, 0]);
210      }
211
212      the_header.intervals_idxes.push(interval_idx);  // Keep the indices into the intervals array for use in rendering
213      intervals.push(line);
214      interval_idx ++;
215    }
216
217    // Compute a set of "merged intervals" for each content_key
218    let rise_fall_edges = [];
219    the_header.intervals_idxes.forEach((i) => {
220      intervals[i].forEach((t0t1) => {
221        if (t0t1[0] <= t0t1[1]) {  // For errored-out method calls, the end time will be set to a value smaller than the start time
222          rise_fall_edges.push([t0t1[0], 0]);  // 0 is a rising edge
223          rise_fall_edges.push([t0t1[1], 1]);  // 1 is a falling edge
224        }
225      })
226    });
227
228    let merged_intervals = [],
229        current_interval = [undefined, undefined, 0];  // start, end, weight
230    rise_fall_edges.sort();
231    let i = 0, level = 0;
232    while (i<rise_fall_edges.length) {
233      let timestamp = rise_fall_edges[i][0];
234      while (i < rise_fall_edges.length && timestamp == rise_fall_edges[i][0]) {
235        switch (rise_fall_edges[i][1]) {
236          case 0: {  // rising edge
237            if (level == 0) {
238              current_interval[0] = timestamp;
239              current_interval[2] ++;
240            }
241            level ++;
242            break;
243          }
244          case 1: {  // falling edge
245            level --;
246            if (level == 0) {
247              current_interval[1] = timestamp;
248              merged_intervals.push(current_interval);
249              current_interval = [undefined, undefined, 0];
250            }
251            break;
252          }
253        }
254        i++;
255      }
256    }
257    the_header.merged_intervals = merged_intervals;
258  }
259
260  // Time shift
261  for (let i = 0; i < intervals.length; i++) {
262    for (let j = 0; j < intervals[i].length; j++) {
263      let x = intervals[i][j];
264      x[0] -= g_StartingSec;
265      x[1] -= g_StartingSec;
266    }
267  }
268  // merged intervals should be time-shifted as well
269  titles.forEach((t) => {
270    if (t.header == true) {
271      t.merged_intervals.forEach((mi) => {
272        mi[0] -= g_StartingSec;
273        mi[1] -= g_StartingSec;
274      })
275    }
276  })
277
278  dbus_timeline_view.Intervals = intervals.slice();
279  dbus_timeline_view.Titles    = titles.slice();
280  dbus_timeline_view.LayoutForOverlappingIntervals();
281}
282
283Canvas_DBus.onmousemove =
284    function(event) {
285  const v = dbus_timeline_view;
286  v.MouseState.x = event.pageX - this.offsetLeft;
287  v.MouseState.y = event.pageY - this.offsetTop;
288  if (v.MouseState.pressed == true &&
289    v.MouseState.hoveredSide == 'timeline') {  // Update highlighted area
290    v.HighlightedRegion.t1 = v.MouseXToTimestamp(v.MouseState.x);
291  }
292  v.OnMouseMove();
293  v.IsCanvasDirty = true;
294
295  v.linked_views.forEach(function(u) {
296    u.MouseState.x = event.pageX - Canvas_DBus.offsetLeft;
297    u.MouseState.y = undefined;                  // Do not highlight any entry or the horizontal scroll bars
298    if (u.MouseState.pressed == true &&
299      v.MouseState.hoveredSide == 'timeline') {  // Update highlighted area
300      u.HighlightedRegion.t1 = u.MouseXToTimestamp(u.MouseState.x);
301    }
302    u.OnMouseMove();
303    u.IsCanvasDirty = true;
304  });
305}
306
307Canvas_DBus.onmousedown = function(event) {
308  if (event.button == 0) {
309    dbus_timeline_view.OnMouseDown();
310  }
311};
312
313Canvas_DBus.onmouseup =
314    function(event) {
315  if (event.button == 0) {
316    dbus_timeline_view.OnMouseUp();
317  }
318}
319
320Canvas_DBus.onmouseleave =
321    function(event) {
322  dbus_timeline_view.OnMouseLeave();
323}
324
325Canvas_DBus.onwheel = function(event) {
326  dbus_timeline_view.OnMouseWheel(event);
327}
328