1// This file performs the file reading step 2// Actual preprocessing is done in dbus_timeline_vis.js 3 4function MyFloatMillisToBigIntUsec(x) { 5 x = ('' + x).split('.'); 6 ret = BigInt(x[0]) * BigInt(1000); 7 return ret; 8} 9 10// When the Open File dialog is completed, the name of the opened file will be passed 11// to this routine. Then the program will do the following: 12// 1. Launch "linecount.py" to get the total packet count in the PCAP file for 13// progress 14// 2. Launch "dbus-pcap" to get the timestamps of each DBus message 15// 3. Launch "dbus-pcap" to get the JSON representation of each DBus message 16// 17function OpenDBusPcapFile(file_name) { 18 // First try to parse using dbus-pcap 19 20 ShowBlocker('Determining the number of packets in the pcap file ...'); 21 const num_lines_py = 22 spawn('python3', ['linecount.py', file_name]); 23 let stdout_num_lines = ''; 24 num_lines_py.stdout.on('data', (data) => { 25 stdout_num_lines += data; 26 }); 27 28 num_lines_py.on('close', (code) => { 29 let num_packets = parseInt(stdout_num_lines.trim()); 30 ShowBlocker('Running dbus-pcap (Pass 1/2, packet timestamps) ...'); 31 const dbus_pcap1 = 32 //spawn('python3', ['dbus-pcap', file_name, '--json', '--progress']); 33 spawn('python3', ['dbus-pcap', file_name]); 34 let stdout1 = ''; 35 let timestamps = []; 36 let count1 = 0; // In the first phase, consecutive newlines indicate a new entry 37 //const r = new RegExp('([0-9]+/[0-9]+) [0-9]+\.[0-9]+:.*'); 38 const r = new RegExp('([0-9]+\.[0-9]+):.*'); 39 40 let is_last_newline = false; // Whether the last scanned character is a newline 41 let last_update_millis = 0; 42 dbus_pcap1.stdout.on('data', (data) => { 43 const s = data.toString(); 44 stdout1 += s; 45 for (let i=0; i<s.length; i++) { 46 const ch = s[i]; 47 let is_new_line = false; 48 if (ch == '\n' || ch == '\r') { 49 is_new_line = true; 50 } 51 if (!is_last_newline && is_new_line) { 52 count1 ++; 53 } 54 is_last_newline = is_new_line; 55 } 56 const millis = Date.now(); 57 if (millis - last_update_millis > 100) { // Refresh at most 10 times per second 58 let pct = parseInt(count1 * 100 / num_packets); 59 ShowBlocker('Running dbus-pcap (Pass 1/2, packet timestamps): ' + count1 + '/' + num_packets + ' (' + pct + '%)'); 60 last_update_millis = millis; 61 } 62 }); 63 64 dbus_pcap1.stderr.on('data', (data) => { 65 console.error(data.toString()); 66 }); 67 68 dbus_pcap1.on('close', (code) => { 69 ShowBlocker('Running dbus-pcap (Pass 2/2, packet contents) ...'); 70 let stdout2 = ''; 71 let count2 = 0; 72 is_last_newline = false; 73 const dbus_pcap2 = 74 spawn('python3', ['dbus-pcap', file_name, '--json']); 75 dbus_pcap2.stdout.on('data', (data) => { 76 const s = data.toString(); 77 stdout2 += s; 78 for (let i=0; i<s.length; i++) { 79 const ch = s[i]; 80 let is_new_line = false; 81 if (ch == '\n' || ch == '\r') { 82 is_new_line = true; 83 } 84 if (!is_last_newline && is_new_line) { 85 count2 ++; 86 } 87 is_last_newline = is_new_line; 88 } 89 const millis = Date.now(); 90 if (millis - last_update_millis > 100) { // Refresh at most 10 times per second 91 let pct = parseInt(count2 * 100 / num_packets); 92 ShowBlocker('Running dbus-pcap (Pass 2/2, packet contents): ' + count2 + '/' + num_packets + ' (' + pct + '%)'); 93 last_update_millis = millis; 94 } 95 }); 96 97 dbus_pcap2.stderr.on('data', (data) => { 98 console.error(data.toString()); 99 }); 100 101 dbus_pcap2.on('close', (code) => { 102 { ShowBlocker('Processing dbus-pcap output ... '); } 103 104 let packets = []; 105 // Parse timestamps 106 let lines = stdout1.split('\n'); 107 for (let i=0; i<lines.length; i++) { 108 let l = lines[i].trim(); 109 if (l.length > 0) { 110 // Timestamp 111 l = l.substr(0, l.indexOf(':')); 112 const ts_usec = parseFloat(l) * 1000.0; 113 if (!isNaN(ts_usec)) { 114 timestamps.push(ts_usec); 115 } else { 116 console.log('not a number: ' + l); 117 } 118 } 119 } 120 121 // JSON 122 lines = stdout2.split('\n'); 123 for (let i=0; i<lines.length; i++) { 124 let l = lines[i].trim(); 125 let parsed = undefined; 126 if (l.length > 0) { 127 try { 128 parsed = JSON.parse(l); 129 } catch (e) { 130 console.log(e); 131 } 132 133 if (parsed == undefined) { 134 try { 135 l = l.replace("NaN", "null"); 136 parsed = JSON.parse(l); 137 } catch (e) { 138 console.log(e); 139 } 140 } 141 142 if (parsed != undefined) { 143 packets.push(parsed); 144 } 145 } 146 } 147 148 Timestamps_DBus = timestamps; 149 150 Data_DBus = packets.slice(); 151 OnGroupByConditionChanged_DBus(); 152 const v = dbus_timeline_view; 153 154 // Will return 2 packages 155 // 1) sensor PropertyChagned signal emissions 156 // 2) everything else 157 let preproc = Preprocess_DBusPcap(packets, timestamps); 158 159 let grouped = Group_DBus(preproc, v.GroupBy); 160 GenerateTimeLine_DBus(grouped); 161 162 dbus_timeline_view.IsCanvasDirty = true; 163 if (dbus_timeline_view.IsEmpty() == false || 164 ipmi_timeline_view.IsEmpty() == false) { 165 dbus_timeline_view.CurrentFileName = file_name; 166 ipmi_timeline_view.CurrentFileName = file_name; 167 HideWelcomeScreen(); 168 ShowDBusTimeline(); 169 ShowIPMITimeline(); 170 ShowNavigation(); 171 UpdateFileNamesString(); 172 } 173 HideBlocker(); 174 175 g_btn_zoom_reset.click(); // Zoom to capture time range 176 }); 177 }); 178 }) 179} 180 181// Input: data and timestamps obtained from 182// Output: Two arrays 183// The first is sensor PropertyChanged emissions only 184// The second is all other DBus message types 185// 186// This function also determines the starting timestamp of the capture 187// 188function Preprocess_DBusPcap(data, timestamps) { 189 // Also clear IPMI entries 190 g_ipmi_parsed_entries = []; 191 192 let ret = []; 193 194 let in_flight = {}; 195 let in_flight_ipmi = {}; 196 197 for (let i = 0; i < data.length; i++) { 198 const packet = data[i]; 199 200 // Fields we are interested in 201 const fixed_header = packet[0]; // is an [Array(5), Array(6)] 202 203 if (fixed_header == undefined) { // for hacked dbus-pcap 204 console.log(packet); 205 continue; 206 } 207 208 const payload = packet[1]; 209 const ty = fixed_header[0][1]; 210 let timestamp = timestamps[i]; 211 let timestamp_end = undefined; 212 const IDX_TIMESTAMP_END = 8; 213 const IDX_MC_OUTCOME = 9; // Outcome of method call 214 215 let serial, path, member, iface, destination, sender, signature = ''; 216 // Same as the format of the Dummy data set 217 218 switch (ty) { 219 case 4: { // signal 220 serial = fixed_header[0][5]; 221 path = fixed_header[1][0][1]; 222 iface = fixed_header[1][1][1]; 223 member = fixed_header[1][2][1]; 224 // signature = fixed_header[1][3][1]; 225 // fixed_header[1] can have variable length. 226 // For example: signal from org.freedesktop.PolicyKit1.Authority can 227 // have only 4 elements, while most others are 5 228 const idx = fixed_header[1].length - 1; 229 sender = fixed_header[1][idx][1]; 230 231 // Ugly fix for: 232 if (sender == "s" || sender == "sss") { 233 sender = packet[1][0]; 234 if (fixed_header[1].length == 6) { 235 // Example: fixed_header is 236 // 0: (2) [7, "org.freedesktop.DBus"] 237 // 1: (2) [6, ":1.1440274"] 238 // 2: (2) [1, "/org/freedesktop/DBus"] 239 // 3: (2) [2, "org.freedesktop.DBus"] 240 // 4: (2) [3, "NameLost"] 241 // 5: (2) [8, "s"] 242 path = fixed_header[1][2][1]; 243 iface = fixed_header[1][3][1]; 244 member = fixed_header[1][4][1]; 245 } else if (fixed_header[1].length == 5) { 246 // Example: fixed_header is 247 // 0: (2) [7, "org.freedesktop.DBus"] 248 // 1: (2) [1, "/org/freedesktop/DBus"] 249 // 2: (2) [2, "org.freedesktop.DBus"] 250 // 3: (2) [3, "NameOwnerChanged"] 251 // 4: (2) [8, "sss"] 252 path = fixed_header[1][1][1]; 253 iface = fixed_header[1][2][1]; 254 member = fixed_header[1][3][1]; 255 } 256 } 257 258 259 destination = '<none>'; 260 timestamp_end = timestamp; 261 let entry = [ 262 'sig', timestamp, serial, sender, destination, path, iface, member, 263 timestamp_end, payload 264 ]; 265 266 // Legacy IPMI interface uses signal for IPMI request 267 if (iface == 'org.openbmc.HostIpmi' && member == 'ReceivedMessage') { 268 console.log('Legacy IPMI interface, request'); 269 } 270 271 ret.push(entry); 272 break; 273 } 274 case 1: { // method call 275 serial = fixed_header[0][5]; 276 path = fixed_header[1][0][1]; 277 member = fixed_header[1][1][1]; 278 iface = fixed_header[1][2][1]; 279 destination = fixed_header[1][3][1]; 280 if (fixed_header[1].length > 5) { 281 sender = fixed_header[1][5][1]; 282 signature = fixed_header[1][4][1]; 283 } else { 284 sender = fixed_header[1][4][1]; 285 } 286 let entry = [ 287 'mc', timestamp, serial, sender, destination, path, iface, member, 288 timestamp_end, payload, packet, '' 289 ]; 290 291 // Legacy IPMI interface uses method call for IPMI response 292 if (iface == 'org.openbmc.HostIpmi' && member == 'sendMessge') { 293 console.log('Legacy IPMI interface, response') 294 } else if ( 295 iface == 'xyz.openbmc_project.Ipmi.Server' && member == 'execute') { 296 let ipmi_entry = { 297 netfn: payload[0], 298 lun: payload[1], 299 cmd: payload[2], 300 request: payload[3], 301 start_usec: MyFloatMillisToBigIntUsec(timestamp), 302 end_usec: 0, 303 response: [] 304 }; 305 in_flight_ipmi[serial] = (ipmi_entry); 306 } 307 308 309 ret.push(entry); 310 in_flight[serial] = (entry); 311 break; 312 } 313 case 2: { // method reply 314 let reply_serial = fixed_header[1][0][1]; 315 if (reply_serial in in_flight) { 316 let x = in_flight[reply_serial]; 317 delete in_flight[reply_serial]; 318 x[IDX_TIMESTAMP_END] = timestamp; 319 x[IDX_MC_OUTCOME] = 'ok'; 320 } 321 322 if (reply_serial in in_flight_ipmi) { 323 let x = in_flight_ipmi[reply_serial]; 324 delete in_flight_ipmi[reply_serial]; 325 if (payload[0] != undefined && payload[0][4] != undefined) { 326 x.response = payload[0][4]; 327 } 328 x.end_usec = MyFloatMillisToBigIntUsec(timestamp); 329 g_ipmi_parsed_entries.push(x); 330 } 331 break; 332 } 333 case 3: { // error reply 334 let reply_serial = fixed_header[1][0][1]; 335 if (reply_serial in in_flight) { 336 let x = in_flight[reply_serial]; 337 delete in_flight[reply_serial]; 338 x[IDX_TIMESTAMP_END] = timestamp; 339 x[IDX_MC_OUTCOME] = 'error'; 340 } 341 } 342 } 343 } 344 345 if (g_ipmi_parsed_entries.length > 0) UpdateLayout(); 346 return ret; 347} 348