Sui Chen | b65280f | 2020-06-30 18:14:03 -0700 | [diff] [blame] | 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) |
| 5 | var Data_DBus = []; |
| 6 | var Timestamps_DBus = []; |
| 7 | |
| 8 | // Main view object |
| 9 | var dbus_timeline_view = new DBusTimelineView(); |
| 10 | var 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() |
| 25 | function draw_timeline_sensors(ctx) { |
| 26 | sensors_timeline_view.Render(ctx); |
| 27 | } |
| 28 | |
| 29 | // Called from renderer.Render() |
| 30 | function draw_timeline_dbus(ctx) { |
| 31 | dbus_timeline_view.Render(ctx); |
| 32 | } |
| 33 | |
| 34 | let Canvas_DBus = document.getElementById('my_canvas_dbus'); |
| 35 | |
| 36 | const 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 | // |
| 53 | const 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 | |
| 100 | function 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 | |
| 139 | function 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 |
| 165 | function 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 tiem |
| 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 | |
| 283 | Canvas_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 | |
| 307 | Canvas_DBus.onmousedown = function(event) { |
| 308 | if (event.button == 0) { |
| 309 | dbus_timeline_view.OnMouseDown(); |
| 310 | } |
| 311 | }; |
| 312 | |
| 313 | Canvas_DBus.onmouseup = |
| 314 | function(event) { |
| 315 | if (event.button == 0) { |
| 316 | dbus_timeline_view.OnMouseUp(); |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | Canvas_DBus.onmouseleave = |
| 321 | function(event) { |
| 322 | dbus_timeline_view.OnMouseLeave(); |
| 323 | } |
| 324 | |
| 325 | Canvas_DBus.onwheel = function(event) { |
| 326 | dbus_timeline_view.OnMouseWheel(event); |
| 327 | } |