| OLD | NEW |
| 1 # Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. | 1 # Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. |
| 2 # | 2 # |
| 3 # Use of this source code is governed by a BSD-style license | 3 # Use of this source code is governed by a BSD-style license |
| 4 # that can be found in the LICENSE file in the root of the source | 4 # that can be found in the LICENSE file in the root of the source |
| 5 # tree. An additional intellectual property rights grant can be found | 5 # tree. An additional intellectual property rights grant can be found |
| 6 # in the file PATENTS. All contributing project authors may | 6 # in the file PATENTS. All contributing project authors may |
| 7 # be found in the AUTHORS file in the root of the source tree. | 7 # be found in the AUTHORS file in the root of the source tree. |
| 8 | 8 |
| 9 """Displays statistics and plots graphs from RTC protobuf dump.""" | 9 """Displays statistics and plots graphs from RTC protobuf dump.""" |
| 10 | 10 |
| 11 from __future__ import division | 11 from __future__ import division |
| 12 from __future__ import print_function | 12 from __future__ import print_function |
| 13 | 13 |
| 14 import collections | 14 import collections |
| 15 import optparse |
| 15 import sys | 16 import sys |
| 16 | 17 |
| 17 import matplotlib.pyplot as plt | 18 import matplotlib.pyplot as plt |
| 18 import numpy | 19 import numpy |
| 19 | 20 |
| 20 import misc | 21 import misc |
| 21 import pb_parse | 22 import pb_parse |
| 22 | 23 |
| 23 | 24 |
| 24 class RTPStatistics(object): | 25 class RTPStatistics(object): |
| (...skipping 13 matching lines...) Expand all Loading... |
| 38 | 39 |
| 39 """ | 40 """ |
| 40 | 41 |
| 41 self.data_points = data_points | 42 self.data_points = data_points |
| 42 self.ssrc_frequencies = misc.normalize_counter( | 43 self.ssrc_frequencies = misc.normalize_counter( |
| 43 collections.Counter([pt.ssrc for pt in self.data_points])) | 44 collections.Counter([pt.ssrc for pt in self.data_points])) |
| 44 self.ssrc_size_table = misc.ssrc_normalized_size_table(self.data_points) | 45 self.ssrc_size_table = misc.ssrc_normalized_size_table(self.data_points) |
| 45 self.bandwidth_kbps = None | 46 self.bandwidth_kbps = None |
| 46 self.smooth_bw_kbps = None | 47 self.smooth_bw_kbps = None |
| 47 | 48 |
| 49 def print_header_statistics(self): |
| 50 print("{:>6}{:>11}{:>11}{:>6}{:>6}{:>3}{:>11}".format( |
| 51 "SeqNo", "TimeStamp", "SendTime", "Size", "PT", "M", "SSRC")) |
| 52 for point in self.data_points: |
| 53 print("{:>6}{:>11}{:>11}{:>6}{:>6}{:>3}{:>11}".format( |
| 54 point.sequence_number, point.timestamp, |
| 55 int(point.arrival_timestamp_ms), point.size, point.payload_type, |
| 56 point.marker_bit, "0x{:x}".format(point.ssrc))) |
| 57 |
| 48 def print_ssrc_info(self, ssrc_id, ssrc): | 58 def print_ssrc_info(self, ssrc_id, ssrc): |
| 49 """Prints packet and size statistics for a given SSRC. | 59 """Prints packet and size statistics for a given SSRC. |
| 50 | 60 |
| 51 Args: | 61 Args: |
| 52 ssrc_id: textual identifier of SSRC printed beside statistics for it. | 62 ssrc_id: textual identifier of SSRC printed beside statistics for it. |
| 53 ssrc: SSRC by which to filter data and display statistics | 63 ssrc: SSRC by which to filter data and display statistics |
| 54 """ | 64 """ |
| 55 filtered_ssrc = [point for point in self.data_points if point.ssrc | 65 filtered_ssrc = [point for point in self.data_points if point.ssrc |
| 56 == ssrc] | 66 == ssrc] |
| 57 payloads = misc.normalize_counter( | 67 payloads = misc.normalize_counter( |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 109 unwrapped_timestamps = misc.unwrap([point.timestamp for point in | 119 unwrapped_timestamps = misc.unwrap([point.timestamp for point in |
| 110 self.data_points], 2**32 - 1) | 120 self.data_points], 2**32 - 1) |
| 111 | 121 |
| 112 for (data_point, timestamp) in zip(self.data_points, | 122 for (data_point, timestamp) in zip(self.data_points, |
| 113 unwrapped_timestamps): | 123 unwrapped_timestamps): |
| 114 data_point.timestamp = timestamp | 124 data_point.timestamp = timestamp |
| 115 | 125 |
| 116 def print_sequence_number_statistics(self): | 126 def print_sequence_number_statistics(self): |
| 117 seq_no_set = set(point.sequence_number for point in | 127 seq_no_set = set(point.sequence_number for point in |
| 118 self.data_points) | 128 self.data_points) |
| 119 print("Missing sequence numbers: {} out of {}".format( | 129 missing_sequence_numbers = max(seq_no_set) - min(seq_no_set) + ( |
| 120 max(seq_no_set) - min(seq_no_set) + 1 - len(seq_no_set), | 130 1 - len(seq_no_set)) |
| 121 len(seq_no_set) | 131 print("Missing sequence numbers: {} out of {} ({:.2f}%)".format( |
| 132 missing_sequence_numbers, |
| 133 len(seq_no_set), |
| 134 100 * missing_sequence_numbers / len(seq_no_set) |
| 122 )) | 135 )) |
| 123 print("Duplicated packets: {}".format(len(self.data_points) - | 136 print("Duplicated packets: {}".format(len(self.data_points) - |
| 124 len(seq_no_set))) | 137 len(seq_no_set))) |
| 125 print("Reordered packets: {}".format( | 138 print("Reordered packets: {}".format( |
| 126 misc.count_reordered([point.sequence_number for point in | 139 misc.count_reordered([point.sequence_number for point in |
| 127 self.data_points]))) | 140 self.data_points]))) |
| 128 | 141 |
| 129 def estimate_frequency(self): | 142 def estimate_frequency(self, always_query_sample_rate): |
| 130 """Estimates frequency and updates data. | 143 """Estimates frequency and updates data. |
| 131 | 144 |
| 132 Guesses the most probable frequency by looking at changes in | 145 Guesses the most probable frequency by looking at changes in |
| 133 timestamps (RFC 3550 section 5.1), calculates clock drifts and | 146 timestamps (RFC 3550 section 5.1), calculates clock drifts and |
| 134 sending time of packets. Updates `self.data_points` with changes | 147 sending time of packets. Updates `self.data_points` with changes |
| 135 in delay and send time. | 148 in delay and send time. |
| 136 """ | 149 """ |
| 137 delta_timestamp = (self.data_points[-1].timestamp - | 150 delta_timestamp = (self.data_points[-1].timestamp - |
| 138 self.data_points[0].timestamp) | 151 self.data_points[0].timestamp) |
| 139 delta_arr_timestamp = float((self.data_points[-1].arrival_timestamp_ms - | 152 delta_arr_timestamp = float((self.data_points[-1].arrival_timestamp_ms - |
| 140 self.data_points[0].arrival_timestamp_ms)) | 153 self.data_points[0].arrival_timestamp_ms)) |
| 141 freq_est = delta_timestamp / delta_arr_timestamp | 154 freq_est = delta_timestamp / delta_arr_timestamp |
| 142 | 155 |
| 143 freq_vec = [8, 16, 32, 48, 90] | 156 freq_vec = [8, 16, 32, 48, 90] |
| 144 freq = None | 157 freq = None |
| 145 for f in freq_vec: | 158 for f in freq_vec: |
| 146 if abs((freq_est - f) / f) < 0.05: | 159 if abs((freq_est - f) / f) < 0.05: |
| 147 freq = f | 160 freq = f |
| 148 | 161 |
| 149 print("Estimated frequency: {}kHz".format(freq_est)) | 162 print("Estimated frequency: {:.3f}kHz".format(freq_est)) |
| 150 if freq is None: | 163 if freq is None or always_query_sample_rate: |
| 151 freq = int(misc.get_input( | 164 if not always_query_sample_rate: |
| 152 "Frequency could not be guessed. Input frequency (in kHz)> ")) | 165 print ("Frequency could not be guessed.", end=" ") |
| 166 freq = int(misc.get_input("Input frequency (in kHz)> ")) |
| 153 else: | 167 else: |
| 154 print("Guessed frequency: {}kHz".format(freq)) | 168 print("Guessed frequency: {}kHz".format(freq)) |
| 155 | 169 |
| 156 for point in self.data_points: | 170 for point in self.data_points: |
| 157 point.real_send_time_ms = (point.timestamp - | 171 point.real_send_time_ms = (point.timestamp - |
| 158 self.data_points[0].timestamp) / freq | 172 self.data_points[0].timestamp) / freq |
| 159 point.delay = point.arrival_timestamp_ms -point.real_send_time_ms | 173 point.delay = point.arrival_timestamp_ms -point.real_send_time_ms |
| 160 | 174 |
| 161 def print_duration_statistics(self): | 175 def print_duration_statistics(self): |
| 162 """Prints delay, clock drift and bitrate statistics.""" | 176 """Prints delay, clock drift and bitrate statistics.""" |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 231 plt.plot([f.real_send_time_ms / 1000 for f in | 245 plt.plot([f.real_send_time_ms / 1000 for f in |
| 232 self.data_points][:len(self.smooth_bw_kbps)], | 246 self.data_points][:len(self.smooth_bw_kbps)], |
| 233 self.smooth_bw_kbps[:len(self.data_points)]) | 247 self.smooth_bw_kbps[:len(self.data_points)]) |
| 234 plt.xlabel("Send time [s]") | 248 plt.xlabel("Send time [s]") |
| 235 plt.ylabel("Bandwidth [kbps]") | 249 plt.ylabel("Bandwidth [kbps]") |
| 236 | 250 |
| 237 plt.show() | 251 plt.show() |
| 238 | 252 |
| 239 | 253 |
| 240 def main(): | 254 def main(): |
| 241 if len(sys.argv) < 2: | 255 usage = "Usage: %prog [options] <filename of rtc event log>" |
| 242 print("Usage: python rtp_analyzer.py <filename of rtc event log>") | 256 parser = optparse.OptionParser(usage=usage) |
| 257 parser.add_option("--dump_header_to_stdout", |
| 258 default=False, action="store_true", |
| 259 help="print header info to stdout; similar to rtp_analyze") |
| 260 parser.add_option("--query_sample_rate", |
| 261 default=False, action="store_true", |
| 262 help="always query user for real sample rate") |
| 263 |
| 264 (options, args) = parser.parse_args() |
| 265 |
| 266 if len(args) < 1: |
| 267 parser.print_help() |
| 243 sys.exit(0) | 268 sys.exit(0) |
| 244 | 269 |
| 245 data_points = pb_parse.parse_protobuf(sys.argv[1]) | 270 data_points = pb_parse.parse_protobuf(args[0]) |
| 246 rtp_stats = RTPStatistics(data_points) | 271 rtp_stats = RTPStatistics(data_points) |
| 272 |
| 273 if options.dump_header_to_stdout: |
| 274 print("Printing header info to stdout.", file=sys.stderr) |
| 275 rtp_stats.print_header_statistics() |
| 276 sys.exit(0) |
| 277 |
| 247 chosen_ssrc = rtp_stats.choose_ssrc() | 278 chosen_ssrc = rtp_stats.choose_ssrc() |
| 248 print("Chosen SSRC: 0X{:X}".format(chosen_ssrc)) | 279 print("Chosen SSRC: 0X{:X}".format(chosen_ssrc)) |
| 249 | 280 |
| 250 rtp_stats.filter_ssrc(chosen_ssrc) | 281 rtp_stats.filter_ssrc(chosen_ssrc) |
| 282 |
| 251 print("Statistics:") | 283 print("Statistics:") |
| 252 rtp_stats.print_sequence_number_statistics() | 284 rtp_stats.print_sequence_number_statistics() |
| 253 rtp_stats.estimate_frequency() | 285 rtp_stats.estimate_frequency(options.query_sample_rate) |
| 254 rtp_stats.print_duration_statistics() | 286 rtp_stats.print_duration_statistics() |
| 255 rtp_stats.remove_reordered() | 287 rtp_stats.remove_reordered() |
| 256 rtp_stats.compute_bandwidth() | 288 rtp_stats.compute_bandwidth() |
| 257 rtp_stats.plot_statistics() | 289 rtp_stats.plot_statistics() |
| 258 | 290 |
| 259 if __name__ == "__main__": | 291 if __name__ == "__main__": |
| 260 main() | 292 main() |
| OLD | NEW |