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