| // This file deals with preprocessing the parsed DBus timeline data. |
| // Data and Timestamps are separate b/c dbus-pcap does not include |
| // timestamps in JSON output so we need to export both formats |
| // (JSON and text) |
| var Data_DBus = []; |
| var Timestamps_DBus = []; |
| |
| // Main view object |
| var dbus_timeline_view = new DBusTimelineView(); |
| var sensors_timeline_view = new DBusTimelineView(); // Same DBusTimelineView type, just that it will have only sensor propertieschanged events |
| |
| // group-by condition changes |
| { |
| const tags = [ |
| 'dbus_column1', 'dbus_column2', 'dbus_column3', 'dbus_column4', |
| 'dbus_column5', 'dbus_column6', 'dbus_column7' |
| ]; |
| for (let i = 0; i < 7; i++) { |
| document.getElementById(tags[i]).addEventListener( |
| 'click', OnGroupByConditionChanged_DBus); |
| } |
| } |
| |
| // Called from renderer.Render() |
| function draw_timeline_sensors(ctx) { |
| sensors_timeline_view.Render(ctx); |
| } |
| |
| // Called from renderer.Render() |
| function draw_timeline_dbus(ctx) { |
| dbus_timeline_view.Render(ctx); |
| } |
| |
| let Canvas_DBus = document.getElementById('my_canvas_dbus'); |
| |
| const IDXES = { |
| 'Type': 0, |
| 'Timestamp': 1, |
| 'Serial': 2, |
| 'Sender': 3, |
| 'Destination': 4, |
| 'Path': 5, |
| 'Interface': 6, |
| 'Member': 7 |
| }; |
| |
| // This "group" is based on the content of the DBus |
| // It is independent of the "group_by" of the meta-data (sender/destination/ |
| // path/interface/member) of a DBus message |
| // |
| // Input is processed message and some basic statistics needed for categorizing |
| // |
| const DBusMessageContentKey = function(msg, cxn_occ) { |
| let ret = undefined; |
| const type = msg[IDXES["Type"]]; |
| const dest = msg[IDXES["Destination"]]; |
| const path = msg[IDXES["Path"]]; |
| const iface = msg[IDXES["Interface"]]; |
| const member = msg[IDXES["Member"]]; |
| const sender = msg[IDXES["Sender"]]; |
| |
| if (sender == "s" || sender == "sss") { |
| console.log(msg) |
| } |
| |
| if (type == "sig") { |
| if (path.indexOf("/xyz/openbmc_project/sensors/") != -1 && |
| iface == "org.freedesktop.DBus.Properties" && |
| member == "PropertiesChanged") { |
| ret = "Sensor PropertiesChanged Signals"; |
| } |
| } else if (type == "mc") { |
| if (dest == "xyz.openbmc_project.Ipmi.Host" && |
| path == "/xyz/openbmc_project/Ipmi" && |
| iface == "xyz.openbmc_project.Ipmi.Server" && |
| member == "execute") { |
| ret = "IPMI Daemon"; |
| } |
| } |
| |
| if (ret == undefined && cxn_occ[sender] <= 10) { |
| ret = "Total 10 messages or less" |
| } |
| |
| if (ret == undefined && type == "mc") { |
| if (path.indexOf("/xyz/openbmc_project/sensors/") == 0 && |
| iface == "org.freedesktop.DBus.Properties" && |
| (member.startsWith("Get") || member.startsWith("Set"))) { |
| ret = "Sensor Get/Set"; |
| } |
| } |
| |
| if (ret == undefined) { |
| ret = "Uncategorized"; |
| } |
| |
| return ret; |
| } |
| |
| function Group_DBus(preprocessed, group_by) { |
| let grouped = {}; // [content key][sort key] -> packet |
| |
| let cxn_occ = {}; // How many times have a specific service appeared? |
| preprocessed.forEach((pp) => { |
| const cxn = pp[IDXES["Sender"]]; |
| if (cxn_occ[cxn] == undefined) { |
| cxn_occ[cxn] = 0; |
| } |
| cxn_occ[cxn]++; |
| }); |
| |
| for (var n = 0; n < preprocessed.length; n++) { |
| var key = ''; |
| for (var i = 0; i < group_by.length; i++) { |
| if (i > 0) key += ' '; |
| key += ('' + preprocessed[n][IDXES[group_by[i]]]); |
| } |
| |
| // "Content Key" is displayed on the "Column Headers" |
| const content_group = DBusMessageContentKey(preprocessed[n], cxn_occ); |
| |
| // Initialize the "Collapsed" array here |
| // TODO: this should ideally not be specific to the dbus_interface_view instance |
| if (dbus_timeline_view.HeaderCollapsed[content_group] == undefined) { |
| dbus_timeline_view.HeaderCollapsed[content_group] = false; // Not collapsed by default |
| } |
| |
| if (grouped[content_group] == undefined) { |
| grouped[content_group] = []; |
| } |
| let grouped1 = grouped[content_group]; |
| |
| if (grouped1[key] == undefined) grouped1[key] = []; |
| grouped1[key].push(preprocessed[n]); |
| } |
| return grouped; |
| } |
| |
| function OnGroupByConditionChanged_DBus() { |
| var tags = [ |
| 'dbus_column1', 'dbus_column2', 'dbus_column3', 'dbus_column4', |
| 'dbus_column5', 'dbus_column6', 'dbus_column7' |
| ]; |
| const v = dbus_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_DBusPcap( |
| Data_DBus, Timestamps_DBus); // should be from dbus_pcap |
| let grouped = Group_DBus(preproc, v.GroupBy); |
| GenerateTimeLine_DBus(grouped); |
| dbus_timeline_view.IsCanvasDirty = true; |
| } |
| |
| // Todo: put g_StartingSec somewhere that's common between sensors and non-sensors |
| function GenerateTimeLine_DBus(grouped) { |
| let intervals = []; |
| let titles = []; |
| g_StartingSec = undefined; |
| |
| // First, turn "content keys" into headers in the flattened layout |
| const content_keys = Object.keys(grouped); |
| |
| const keys = Object.keys(grouped); |
| let sortedKeys = keys.slice(); |
| |
| let interval_idx = 0; // The overall index into the intervals array |
| |
| for (let x=0; x<content_keys.length; x++) { |
| const content_key = content_keys[x]; |
| // Per-content key |
| const grouped1 = grouped[content_key]; |
| const keys1 = Object.keys(grouped1); |
| |
| let the_header = { "header":true, "title":content_key, "intervals_idxes":[] }; |
| titles.push(the_header); |
| // TODO: this currently depends on the dbus_timeline_view instance |
| const collapsed = dbus_timeline_view.HeaderCollapsed[content_key]; |
| |
| for (let i = 0; i < keys1.length; i++) { |
| // The Title array controls which lines are drawn. If we con't push the header |
| // it will not be drawn (thus giving a "collapsed" visual effect.) |
| if (!collapsed) { |
| titles.push({ "header":false, "title":keys1[i], "intervals_idxes":[interval_idx] }); |
| } |
| |
| |
| line = []; |
| for (let j = 0; j < grouped1[keys1[i]].length; j++) { |
| let entry = grouped1[keys1[i]][j]; |
| let t0 = parseFloat(entry[1]) / 1000.0; |
| let t1 = parseFloat(entry[8]) / 1000.0; |
| |
| // Modify time shift delta if IPMI dataset is loaded first |
| if (g_StartingSec == undefined) { |
| g_StartingSec = t0; |
| } |
| g_StartingSec = Math.min(g_StartingSec, t0); |
| const outcome = entry[9]; |
| line.push([t0, t1, entry, outcome, 0]); |
| } |
| |
| the_header.intervals_idxes.push(interval_idx); // Keep the indices into the intervals array for use in rendering |
| intervals.push(line); |
| interval_idx ++; |
| } |
| |
| // Compute a set of "merged intervals" for each content_key |
| let rise_fall_edges = []; |
| the_header.intervals_idxes.forEach((i) => { |
| intervals[i].forEach((t0t1) => { |
| if (t0t1[0] <= t0t1[1]) { // For errored-out method calls, the end time will be set to a value smaller than the start tiem |
| rise_fall_edges.push([t0t1[0], 0]); // 0 is a rising edge |
| rise_fall_edges.push([t0t1[1], 1]); // 1 is a falling edge |
| } |
| }) |
| }); |
| |
| let merged_intervals = [], |
| current_interval = [undefined, undefined, 0]; // start, end, weight |
| rise_fall_edges.sort(); |
| let i = 0, level = 0; |
| while (i<rise_fall_edges.length) { |
| let timestamp = rise_fall_edges[i][0]; |
| while (i < rise_fall_edges.length && timestamp == rise_fall_edges[i][0]) { |
| switch (rise_fall_edges[i][1]) { |
| case 0: { // rising edge |
| if (level == 0) { |
| current_interval[0] = timestamp; |
| current_interval[2] ++; |
| } |
| level ++; |
| break; |
| } |
| case 1: { // falling edge |
| level --; |
| if (level == 0) { |
| current_interval[1] = timestamp; |
| merged_intervals.push(current_interval); |
| current_interval = [undefined, undefined, 0]; |
| } |
| break; |
| } |
| } |
| i++; |
| } |
| } |
| the_header.merged_intervals = merged_intervals; |
| } |
| |
| // Time shift |
| for (let i = 0; i < intervals.length; i++) { |
| for (let j = 0; j < intervals[i].length; j++) { |
| let x = intervals[i][j]; |
| x[0] -= g_StartingSec; |
| x[1] -= g_StartingSec; |
| } |
| } |
| // merged intervals should be time-shifted as well |
| titles.forEach((t) => { |
| if (t.header == true) { |
| t.merged_intervals.forEach((mi) => { |
| mi[0] -= g_StartingSec; |
| mi[1] -= g_StartingSec; |
| }) |
| } |
| }) |
| |
| dbus_timeline_view.Intervals = intervals.slice(); |
| dbus_timeline_view.Titles = titles.slice(); |
| dbus_timeline_view.LayoutForOverlappingIntervals(); |
| } |
| |
| Canvas_DBus.onmousemove = |
| function(event) { |
| const v = dbus_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_DBus.offsetLeft; |
| u.MouseState.y = undefined; // Do not highlight any entry or the horizontal scroll bars |
| if (u.MouseState.pressed == true && |
| v.MouseState.hoveredSide == 'timeline') { // Update highlighted area |
| u.HighlightedRegion.t1 = u.MouseXToTimestamp(u.MouseState.x); |
| } |
| u.OnMouseMove(); |
| u.IsCanvasDirty = true; |
| }); |
| } |
| |
| Canvas_DBus.onmousedown = function(event) { |
| if (event.button == 0) { |
| dbus_timeline_view.OnMouseDown(); |
| } |
| }; |
| |
| Canvas_DBus.onmouseup = |
| function(event) { |
| if (event.button == 0) { |
| dbus_timeline_view.OnMouseUp(); |
| } |
| } |
| |
| Canvas_DBus.onmouseleave = |
| function(event) { |
| dbus_timeline_view.OnMouseLeave(); |
| } |
| |
| Canvas_DBus.onwheel = function(event) { |
| dbus_timeline_view.OnMouseWheel(event); |
| } |