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 |