| // This file performs the file reading step |
| // Actual preprocessing is done in dbus_timeline_vis.js |
| |
| function MyFloatMillisToBigIntUsec(x) { |
| x = ('' + x).split('.'); |
| ret = BigInt(x[0]) * BigInt(1000); |
| return ret; |
| } |
| |
| // When the Open File dialog is completed, the name of the opened file will be passed |
| // to this routine. Then the program will do the following: |
| // 1. Launch "linecount.py" to get the total packet count in the PCAP file for |
| // progress |
| // 2. Launch "dbus-pcap" to get the timestamps of each DBus message |
| // 3. Launch "dbus-pcap" to get the JSON representation of each DBus message |
| // |
| function OpenDBusPcapFile(file_name) { |
| // First try to parse using dbus-pcap |
| |
| ShowBlocker('Determining the number of packets in the pcap file ...'); |
| const num_lines_py = |
| spawn('python3', ['linecount.py', file_name]); |
| let stdout_num_lines = ''; |
| num_lines_py.stdout.on('data', (data) => { |
| stdout_num_lines += data; |
| }); |
| |
| num_lines_py.on('close', (code) => { |
| let num_packets = parseInt(stdout_num_lines.trim()); |
| ShowBlocker('Running dbus-pcap (Pass 1/2, packet timestamps) ...'); |
| const dbus_pcap1 = |
| //spawn('python3', ['dbus-pcap', file_name, '--json', '--progress']); |
| spawn('python3', ['dbus-pcap', file_name]); |
| let stdout1 = ''; |
| let timestamps = []; |
| let count1 = 0; // In the first phase, consecutive newlines indicate a new entry |
| //const r = new RegExp('([0-9]+/[0-9]+) [0-9]+\.[0-9]+:.*'); |
| const r = new RegExp('([0-9]+\.[0-9]+):.*'); |
| |
| let is_last_newline = false; // Whether the last scanned character is a newline |
| let last_update_millis = 0; |
| dbus_pcap1.stdout.on('data', (data) => { |
| const s = data.toString(); |
| stdout1 += s; |
| for (let i=0; i<s.length; i++) { |
| const ch = s[i]; |
| let is_new_line = false; |
| if (ch == '\n' || ch == '\r') { |
| is_new_line = true; |
| } |
| if (!is_last_newline && is_new_line) { |
| count1 ++; |
| } |
| is_last_newline = is_new_line; |
| } |
| const millis = Date.now(); |
| if (millis - last_update_millis > 100) { // Refresh at most 10 times per second |
| let pct = parseInt(count1 * 100 / num_packets); |
| ShowBlocker('Running dbus-pcap (Pass 1/2, packet timestamps): ' + count1 + '/' + num_packets + ' (' + pct + '%)'); |
| last_update_millis = millis; |
| } |
| }); |
| |
| dbus_pcap1.stderr.on('data', (data) => { |
| console.err(data.toString()); |
| }); |
| |
| dbus_pcap1.on('close', (code) => { |
| ShowBlocker('Running dbus-pcap (Pass 2/2, packet contents) ...'); |
| let stdout2 = ''; |
| let count2 = 0; |
| is_last_newline = false; |
| const dbus_pcap2 = |
| spawn('python3', ['dbus-pcap', file_name, '--json']); |
| dbus_pcap2.stdout.on('data', (data) => { |
| const s = data.toString(); |
| stdout2 += s; |
| for (let i=0; i<s.length; i++) { |
| const ch = s[i]; |
| let is_new_line = false; |
| if (ch == '\n' || ch == '\r') { |
| is_new_line = true; |
| } |
| if (!is_last_newline && is_new_line) { |
| count2 ++; |
| } |
| is_last_newline = is_new_line; |
| } |
| const millis = Date.now(); |
| if (millis - last_update_millis > 100) { // Refresh at most 10 times per second |
| let pct = parseInt(count2 * 100 / num_packets); |
| ShowBlocker('Running dbus-pcap (Pass 2/2, packet contents): ' + count2 + '/' + num_packets + ' (' + pct + '%)'); |
| last_update_millis = millis; |
| } |
| }); |
| |
| dbus_pcap2.stderr.on('data', (data) => { |
| console.err(data.toString()); |
| }); |
| |
| dbus_pcap2.on('close', (code) => { |
| { ShowBlocker('Processing dbus-pcap output ... '); } |
| |
| let packets = []; |
| // Parse timestamps |
| let lines = stdout1.split('\n'); |
| for (let i=0; i<lines.length; i++) { |
| let l = lines[i].trim(); |
| if (l.length > 0) { |
| // Timestamp |
| l = l.substr(0, l.indexOf(':')); |
| const ts_usec = parseFloat(l) * 1000.0; |
| if (!isNaN(ts_usec)) { |
| timestamps.push(ts_usec); |
| } else { |
| console.log('not a number: ' + l); |
| } |
| } |
| } |
| |
| // JSON |
| lines = stdout2.split('\n'); |
| for (let i=0; i<lines.length; i++) { |
| let l = lines[i].trim(); |
| let parsed = undefined; |
| if (l.length > 0) { |
| try { |
| parsed = JSON.parse(l); |
| } catch (e) { |
| console.log(e); |
| } |
| |
| if (parsed == undefined) { |
| try { |
| l = l.replace("NaN", "null"); |
| parsed = JSON.parse(l); |
| } catch (e) { |
| console.log(e); |
| } |
| } |
| |
| if (parsed != undefined) { |
| packets.push(parsed); |
| } |
| } |
| } |
| |
| Timestamps_DBus = timestamps; |
| |
| Data_DBus = packets.slice(); |
| OnGroupByConditionChanged_DBus(); |
| const v = dbus_timeline_view; |
| |
| // Will return 2 packages |
| // 1) sensor PropertyChagned signal emissions |
| // 2) everything else |
| let preproc = Preprocess_DBusPcap(packets, timestamps); |
| |
| let grouped = Group_DBus(preproc, v.GroupBy); |
| GenerateTimeLine_DBus(grouped); |
| |
| dbus_timeline_view.IsCanvasDirty = true; |
| if (dbus_timeline_view.IsEmpty() == false || |
| ipmi_timeline_view.IsEmpty() == false) { |
| dbus_timeline_view.CurrentFileName = file_name; |
| ipmi_timeline_view.CurrentFileName = file_name; |
| HideWelcomeScreen(); |
| ShowDBusTimeline(); |
| ShowIPMITimeline(); |
| ShowNavigation(); |
| UpdateFileNamesString(); |
| } |
| HideBlocker(); |
| |
| g_btn_zoom_reset.click(); // Zoom to capture time range |
| }); |
| }); |
| }) |
| } |
| |
| // Input: data and timestamps obtained from |
| // Output: Two arrays |
| // The first is sensor PropertyChanged emissions only |
| // The second is all other DBus message types |
| // |
| // This function also determines the starting timestamp of the capture |
| // |
| function Preprocess_DBusPcap(data, timestamps) { |
| // Also clear IPMI entries |
| g_ipmi_parsed_entries = []; |
| |
| let ret = []; |
| |
| let in_flight = {}; |
| let in_flight_ipmi = {}; |
| |
| for (let i = 0; i < data.length; i++) { |
| const packet = data[i]; |
| |
| // Fields we are interested in |
| const fixed_header = packet[0]; // is an [Array(5), Array(6)] |
| |
| if (fixed_header == undefined) { // for hacked dbus-pcap |
| console.log(packet); |
| continue; |
| } |
| |
| const payload = packet[1]; |
| const ty = fixed_header[0][1]; |
| let timestamp = timestamps[i]; |
| let timestamp_end = undefined; |
| const IDX_TIMESTAMP_END = 8; |
| const IDX_MC_OUTCOME = 9; // Outcome of method call |
| |
| let serial, path, member, iface, destination, sender, signature = ''; |
| // Same as the format of the Dummy data set |
| |
| switch (ty) { |
| case 4: { // signal |
| serial = fixed_header[0][5]; |
| path = fixed_header[1][0][1]; |
| iface = fixed_header[1][1][1]; |
| member = fixed_header[1][2][1]; |
| // signature = fixed_header[1][3][1]; |
| // fixed_header[1] can have variable length. |
| // For example: signal from org.freedesktop.PolicyKit1.Authority can |
| // have only 4 elements, while most others are 5 |
| const idx = fixed_header[1].length - 1; |
| sender = fixed_header[1][idx][1]; |
| |
| // Ugly fix for: |
| if (sender == "s" || sender == "sss") { |
| sender = packet[1][0]; |
| if (fixed_header[1].length == 6) { |
| // Example: fixed_header is |
| // 0: (2) [7, "org.freedesktop.DBus"] |
| // 1: (2) [6, ":1.1440274"] |
| // 2: (2) [1, "/org/freedesktop/DBus"] |
| // 3: (2) [2, "org.freedesktop.DBus"] |
| // 4: (2) [3, "NameLost"] |
| // 5: (2) [8, "s"] |
| path = fixed_header[1][2][1]; |
| iface = fixed_header[1][3][1]; |
| member = fixed_header[1][4][1]; |
| } else if (fixed_header[1].length == 5) { |
| // Example: fixed_header is |
| // 0: (2) [7, "org.freedesktop.DBus"] |
| // 1: (2) [1, "/org/freedesktop/DBus"] |
| // 2: (2) [2, "org.freedesktop.DBus"] |
| // 3: (2) [3, "NameOwnerChanged"] |
| // 4: (2) [8, "sss"] |
| path = fixed_header[1][1][1]; |
| iface = fixed_header[1][2][1]; |
| member = fixed_header[1][3][1]; |
| } |
| } |
| |
| |
| destination = '<none>'; |
| timestamp_end = timestamp; |
| let entry = [ |
| 'sig', timestamp, serial, sender, destination, path, iface, member, |
| timestamp_end, payload |
| ]; |
| |
| // Legacy IPMI interface uses signal for IPMI request |
| if (iface == 'org.openbmc.HostIpmi' && member == 'ReceivedMessage') { |
| console.log('Legacy IPMI interface, request'); |
| } |
| |
| ret.push(entry); |
| break; |
| } |
| case 1: { // method call |
| serial = fixed_header[0][5]; |
| path = fixed_header[1][0][1]; |
| member = fixed_header[1][1][1]; |
| iface = fixed_header[1][2][1]; |
| destination = fixed_header[1][3][1]; |
| if (fixed_header[1].length > 5) { |
| sender = fixed_header[1][5][1]; |
| signature = fixed_header[1][4][1]; |
| } else { |
| sender = fixed_header[1][4][1]; |
| } |
| let entry = [ |
| 'mc', timestamp, serial, sender, destination, path, iface, member, |
| timestamp_end, payload, '' |
| ]; |
| |
| // Legacy IPMI interface uses method call for IPMI response |
| if (iface == 'org.openbmc.HostIpmi' && member == 'sendMessge') { |
| console.log('Legacy IPMI interface, response') |
| } else if ( |
| iface == 'xyz.openbmc_project.Ipmi.Server' && member == 'execute') { |
| let ipmi_entry = { |
| netfn: payload[0], |
| lun: payload[1], |
| cmd: payload[2], |
| request: payload[3], |
| start_usec: MyFloatMillisToBigIntUsec(timestamp), |
| end_usec: 0, |
| response: [] |
| }; |
| in_flight_ipmi[serial] = (ipmi_entry); |
| } |
| |
| |
| ret.push(entry); |
| in_flight[serial] = (entry); |
| break; |
| } |
| case 2: { // method reply |
| let reply_serial = fixed_header[1][0][1]; |
| if (reply_serial in in_flight) { |
| let x = in_flight[reply_serial]; |
| delete in_flight[reply_serial]; |
| x[IDX_TIMESTAMP_END] = timestamp; |
| x[IDX_MC_OUTCOME] = 'ok'; |
| } |
| |
| if (reply_serial in in_flight_ipmi) { |
| let x = in_flight_ipmi[reply_serial]; |
| delete in_flight_ipmi[reply_serial]; |
| if (payload[0] != undefined && payload[0][4] != undefined) { |
| x.response = payload[0][4]; |
| } |
| x.end_usec = MyFloatMillisToBigIntUsec(timestamp); |
| g_ipmi_parsed_entries.push(x); |
| } |
| break; |
| } |
| case 3: { // error reply |
| let reply_serial = fixed_header[1][0][1]; |
| if (reply_serial in in_flight) { |
| let x = in_flight[reply_serial]; |
| delete in_flight[reply_serial]; |
| x[IDX_TIMESTAMP_END] = timestamp; |
| x[IDX_MC_OUTCOME] = 'error'; |
| } |
| } |
| } |
| } |
| |
| if (g_ipmi_parsed_entries.length > 0) UpdateLayout(); |
| return ret; |
| } |