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 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 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