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 optparse |
16 import sys | 16 import sys |
17 | 17 |
18 import matplotlib.pyplot as plt | 18 import matplotlib.pyplot as plt |
19 import numpy | 19 import numpy |
20 | 20 |
21 import misc | 21 import misc |
22 import pb_parse | 22 import pb_parse |
23 | 23 |
24 | 24 |
25 class RTPStatistics(object): | 25 class RTPStatistics(object): |
26 """Has methods for calculating and plotting RTP stream statistics.""" | 26 """Has methods for calculating and plotting RTP stream statistics.""" |
27 | 27 |
28 BANDWIDTH_SMOOTHING_WINDOW_SIZE = 10 | 28 BANDWIDTH_SMOOTHING_WINDOW_SIZE = 10 |
| 29 PLOT_RESOLUTION_MS = 50 |
29 | 30 |
30 def __init__(self, data_points): | 31 def __init__(self, data_points): |
31 """Initializes object with data_points and computes simple statistics. | 32 """Initializes object with data_points and computes simple statistics. |
32 | 33 |
33 Computes percentages of number of packets and packet sizes by | 34 Computes percentages of number of packets and packet sizes by |
34 SSRC. | 35 SSRC. |
35 | 36 |
36 Args: | 37 Args: |
37 data_points: list of pb_parse.DataPoints on which statistics are | 38 data_points: list of pb_parse.DataPoints on which statistics are |
38 calculated. | 39 calculated. |
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
163 if freq is None or always_query_sample_rate: | 164 if freq is None or always_query_sample_rate: |
164 if not always_query_sample_rate: | 165 if not always_query_sample_rate: |
165 print ("Frequency could not be guessed.", end=" ") | 166 print ("Frequency could not be guessed.", end=" ") |
166 freq = int(misc.get_input("Input frequency (in kHz)> ")) | 167 freq = int(misc.get_input("Input frequency (in kHz)> ")) |
167 else: | 168 else: |
168 print("Guessed frequency: {}kHz".format(freq)) | 169 print("Guessed frequency: {}kHz".format(freq)) |
169 | 170 |
170 for point in self.data_points: | 171 for point in self.data_points: |
171 point.real_send_time_ms = (point.timestamp - | 172 point.real_send_time_ms = (point.timestamp - |
172 self.data_points[0].timestamp) / freq | 173 self.data_points[0].timestamp) / freq |
173 point.delay = point.arrival_timestamp_ms -point.real_send_time_ms | 174 point.delay = point.arrival_timestamp_ms - point.real_send_time_ms |
174 | 175 |
175 def print_duration_statistics(self): | 176 def print_duration_statistics(self): |
176 """Prints delay, clock drift and bitrate statistics.""" | 177 """Prints delay, clock drift and bitrate statistics.""" |
177 | 178 |
178 min_delay = min(point.delay for point in self.data_points) | 179 min_delay = min(point.delay for point in self.data_points) |
179 | 180 |
180 for point in self.data_points: | 181 for point in self.data_points: |
181 point.absdelay = point.delay - min_delay | 182 point.absdelay = point.delay - min_delay |
182 | 183 |
183 stream_duration_sender = self.data_points[-1].real_send_time_ms / 1000 | 184 stream_duration_sender = self.data_points[-1].real_send_time_ms / 1000 |
(...skipping 30 matching lines...) Expand all Loading... |
214 last = point | 215 last = point |
215 self.data_points = data_points_ordered | 216 self.data_points = data_points_ordered |
216 | 217 |
217 def compute_bandwidth(self): | 218 def compute_bandwidth(self): |
218 """Computes bandwidth averaged over several consecutive packets. | 219 """Computes bandwidth averaged over several consecutive packets. |
219 | 220 |
220 The number of consecutive packets used in the average is | 221 The number of consecutive packets used in the average is |
221 BANDWIDTH_SMOOTHING_WINDOW_SIZE. Averaging is done with | 222 BANDWIDTH_SMOOTHING_WINDOW_SIZE. Averaging is done with |
222 numpy.correlate. | 223 numpy.correlate. |
223 """ | 224 """ |
224 self.bandwidth_kbps = [] | 225 start_ms = self.data_points[0].real_send_time_ms |
225 for i in range(len(self.data_points) - 1): | 226 stop_ms = self.data_points[-1].real_send_time_ms |
226 self.bandwidth_kbps.append(self.data_points[i].size * 8 / | 227 (self.bandwidth_kbps, _) = numpy.histogram( |
227 (self.data_points[i + | 228 [point.real_send_time_ms for point in self.data_points], |
228 1].real_send_time_ms - | 229 bins=numpy.arange(start_ms, stop_ms, |
229 self.data_points[i].real_send_time_ms) | 230 RTPStatistics.PLOT_RESOLUTION_MS), |
230 ) | 231 weights=[point.size * 8 / RTPStatistics.PLOT_RESOLUTION_MS |
| 232 for point in self.data_points] |
| 233 ) |
231 correlate_filter = (numpy.ones( | 234 correlate_filter = (numpy.ones( |
232 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE) / | 235 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE) / |
233 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE) | 236 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE) |
234 self.smooth_bw_kbps = numpy.correlate(self.bandwidth_kbps, correlate_filter) | 237 self.smooth_bw_kbps = numpy.correlate(self.bandwidth_kbps, correlate_filter) |
235 | 238 |
236 def plot_statistics(self): | 239 def plot_statistics(self): |
237 """Plots changes in delay and average bandwidth.""" | 240 """Plots changes in delay and average bandwidth.""" |
| 241 |
| 242 start_ms = self.data_points[0].real_send_time_ms |
| 243 stop_ms = self.data_points[-1].real_send_time_ms |
| 244 time_axis = numpy.arange(start_ms / 1000, stop_ms / 1000, |
| 245 RTPStatistics.PLOT_RESOLUTION_MS / 1000) |
| 246 |
| 247 delay = calculate_delay(start_ms, stop_ms, |
| 248 RTPStatistics.PLOT_RESOLUTION_MS, |
| 249 self.data_points) |
| 250 |
238 plt.figure(1) | 251 plt.figure(1) |
239 plt.plot([f.real_send_time_ms / 1000 for f in self.data_points], | 252 plt.plot(time_axis, delay) |
240 [f.absdelay for f in self.data_points]) | |
241 plt.xlabel("Send time [s]") | 253 plt.xlabel("Send time [s]") |
242 plt.ylabel("Relative transport delay [ms]") | 254 plt.ylabel("Relative transport delay [ms]") |
243 | 255 |
244 plt.figure(2) | 256 plt.figure(2) |
245 plt.plot([f.real_send_time_ms / 1000 for f in | 257 plt.plot(time_axis[:len(self.smooth_bw_kbps)], self.smooth_bw_kbps) |
246 self.data_points][:len(self.smooth_bw_kbps)], | |
247 self.smooth_bw_kbps[:len(self.data_points)]) | |
248 plt.xlabel("Send time [s]") | 258 plt.xlabel("Send time [s]") |
249 plt.ylabel("Bandwidth [kbps]") | 259 plt.ylabel("Bandwidth [kbps]") |
250 | 260 |
251 plt.show() | 261 plt.show() |
252 | 262 |
253 | 263 |
| 264 def calculate_delay(start, stop, step, points): |
| 265 """Quantizes the time coordinates for the delay. |
| 266 |
| 267 Quantizes points by rounding the timestamps downwards to the nearest |
| 268 point in the time sequence start, start+step, start+2*step... Takes |
| 269 the average of the delays of points rounded to the same. Returns |
| 270 masked array, in which time points with no value are masked. |
| 271 |
| 272 """ |
| 273 grouped_delays = [[] for _ in numpy.arange(start, stop, step)] |
| 274 rounded_value_index = lambda x: int((x - start) / step) |
| 275 for point in points: |
| 276 grouped_delays[rounded_value_index(point.real_send_time_ms) |
| 277 ].append(point.absdelay) |
| 278 regularized_delays = [numpy.average(arr) if arr else None for arr in |
| 279 grouped_delays] |
| 280 return numpy.ma.masked_values(regularized_delays, None) |
| 281 |
| 282 |
254 def main(): | 283 def main(): |
255 usage = "Usage: %prog [options] <filename of rtc event log>" | 284 usage = "Usage: %prog [options] <filename of rtc event log>" |
256 parser = optparse.OptionParser(usage=usage) | 285 parser = optparse.OptionParser(usage=usage) |
257 parser.add_option("--dump_header_to_stdout", | 286 parser.add_option("--dump_header_to_stdout", |
258 default=False, action="store_true", | 287 default=False, action="store_true", |
259 help="print header info to stdout; similar to rtp_analyze") | 288 help="print header info to stdout; similar to rtp_analyze") |
260 parser.add_option("--query_sample_rate", | 289 parser.add_option("--query_sample_rate", |
261 default=False, action="store_true", | 290 default=False, action="store_true", |
262 help="always query user for real sample rate") | 291 help="always query user for real sample rate") |
263 | 292 |
(...skipping 19 matching lines...) Expand all Loading... |
283 print("Statistics:") | 312 print("Statistics:") |
284 rtp_stats.print_sequence_number_statistics() | 313 rtp_stats.print_sequence_number_statistics() |
285 rtp_stats.estimate_frequency(options.query_sample_rate) | 314 rtp_stats.estimate_frequency(options.query_sample_rate) |
286 rtp_stats.print_duration_statistics() | 315 rtp_stats.print_duration_statistics() |
287 rtp_stats.remove_reordered() | 316 rtp_stats.remove_reordered() |
288 rtp_stats.compute_bandwidth() | 317 rtp_stats.compute_bandwidth() |
289 rtp_stats.plot_statistics() | 318 rtp_stats.plot_statistics() |
290 | 319 |
291 if __name__ == "__main__": | 320 if __name__ == "__main__": |
292 main() | 321 main() |
OLD | NEW |