Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(85)

Side by Side Diff: tools/py_event_log_analyzer/rtp_analyzer.py

Issue 1999113002: New rtc dump analyzing tool in Python (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Unit test for misc.unwrap Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
2 #
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
5 # tree. An additional intellectual property rights grant can be found
6 # in the file PATENTS. All contributing project authors may
7 # be found in the AUTHORS file in the root of the source tree.
8
9 """Displays statistics and plots graphs from RTC protobuf dump."""
10
11 from __future__ import division
12 from __future__ import print_function
13
14 import collections
15 import sys
16 import matplotlib.pyplot as plt
17 import misc
18 import numpy
19 import pb_parse
20
21
22 class RTPStatistics(object):
23 """Has methods for calculating and plotting statistics for RTP
24 packets.
25 """
26
27 BANDWIDTH_SMOOTHING_WINDOW_SIZE = 10
28
29 def __init__(self, data_points):
30 """Initializes object with data_points and computes simple statistics:
31 percentages of packets and packet sizes by SSRC.
32
33 Args:
34 data_points: list of pb_parse.DataPoints on which statistics are
35 calculated.
36
37 """
38
39 self.data_points = data_points
40 self.ssrc_frequencies = misc.normalize_counter(
41 collections.Counter([pt.ssrc for pt in self.data_points]))
42 self.ssrc_size_table = misc.ssrc_normalized_size_table(self.data_points)
43 self.bandwidth_kbps = None
44 self.smooth_bw_kbps = None
45
46 def print_ssrc_info(self, ssrc_id, ssrc):
47 """Prints packet and size statistics for a given SSRC.
48
49 Args:
50 ssrc_id: textual identifier of SSRC printed beside statistics for it.
51 ssrc: SSRC by which to filter data and display statistics
52 """
53 filtered_ssrc = [x for x in self.data_points if x.ssrc == ssrc]
54 payloads = misc.normalize_counter(
55 collections.Counter([x.payload_type for x in filtered_ssrc]))
56
57 payload_info = "payload type(s): {}".format(", ".join(str(x) for x
58 in payloads))
59 print("{} 0x{:x} {}, {:.2f}% packets, {:.2f}% data".format(
60 ssrc_id, ssrc, payload_info, self.ssrc_frequencies[ssrc] * 100,
61 self.ssrc_size_table[ssrc] * 100))
62 print(" packet sizes:")
63 (bin_counts, bin_bounds) = numpy.histogram([x.size for x in
64 filtered_ssrc], bins=5,
65 density=False)
66 bin_proportions = bin_counts / sum(bin_counts)
67 print("\n".join([
68 " {:.1f} - {:.1f}: {:.2f}%".format(bin_bounds[i], bin_bounds[i + 1],
69 bin_proportions[i] * 100)
70 for i in range(len(bin_proportions))
71 ]))
72
73 def choose_ssrc(self):
74 """Queries user for SSRC."""
75
76 if len(self.ssrc_frequencies) == 1:
77 chosen_ssrc = self.ssrc_frequencies[0][-1]
78 self.print_ssrc_info("", chosen_ssrc)
79 return chosen_ssrc
80
81 for (i, ssrc) in enumerate(self.ssrc_frequencies):
82 self.print_ssrc_info(i, ssrc)
83
84 while True:
85 chosen_index = int(misc.get_input("choose one> "))
86 if 0 <= chosen_index < len(self.ssrc_frequencies):
87 return list(self.ssrc_frequencies)[chosen_index]
88 else:
89 print("Invalid index!")
90
91 def filter_ssrc(self, chosen_ssrc):
92 """Filters and wraps data points.
93
94 Removes data points with `ssrc != chosen_ssrc`. Unwraps sequence
95 numbers and timestamps for the chosen selection.
96 """
97 self.data_points = [x for x in self.data_points if x.ssrc ==
98 chosen_ssrc]
99 unwrapped_sequence_numbers = misc.unwrap([x.sequence_number for x in
100 self.data_points],
101 2**16 - 1)
102 for (data_point, sequence_number) in zip(self.data_points,
103 unwrapped_sequence_numbers):
104 data_point.sequence_number = sequence_number
105
106 unwrapped_timestamps = misc.unwrap([x.timestamp for x in self.data_points],
107 2**32 - 1)
108
109 for (data_point, timestamp) in zip(self.data_points,
110 unwrapped_timestamps):
111 data_point.timestamp = timestamp
112
113 def print_sequence_number_statistics(self):
114 seq_no_set = set(x.sequence_number for x in self.data_points)
115 print("Missing sequence numbers: {} out of {}".format(
116 max(seq_no_set) - min(seq_no_set) + 1 - len(seq_no_set),
117 len(seq_no_set)
118 ))
119 print("Duplicated packets: {}".format(len(self.data_points) -
120 len(seq_no_set)))
121 print("Reordered packets: {}".format(
122 misc.count_reordered([x.sequence_number for x in self.data_points])))
123
124 def estimate_frequency(self):
125 """Estimates frequency and updates data.
126
127 Guesses the most probable frequency by looking at changes in
128 timestamps (RFC 3550 section 5.1), calculates clock drifts and
129 sending time of packets. Updates `self.data_points` with changes
130 in delay and send time.
131 """
132 delta_timestamp = (self.data_points[-1].timestamp -
133 self.data_points[0].timestamp)
134 delta_arr_timestamp = float((self.data_points[-1].arrival_timestamp_ms -
135 self.data_points[0].arrival_timestamp_ms))
136 fs_est = delta_timestamp / delta_arr_timestamp
137
138 fs_vec = [8, 16, 32, 48, 90]
139 fs = None
140 for f in fs_vec:
141 if abs((fs_est - f) / f) < 0.05:
142 fs = f
143
144 print("Estimated frequency: {}kHz".format(fs_est))
145 if fs is None:
146 fs = int(misc.get_input(
147 "Frequency could not be guessed. Input frequency (in kHz)> "))
148 else:
149 print("Guessed frequency: {}kHz".format(fs))
150
151 for f in self.data_points:
152 f.real_send_time_ms = (f.timestamp -
153 self.data_points[0].timestamp) / fs
154 f.delay = f.arrival_timestamp_ms - f.real_send_time_ms
155
156 def print_duration_statistics(self):
157 """Prints delay, clock drift and bitrate statistics.
158 """
159
160 min_delay = min(f.delay for f in self.data_points)
161
162 for f in self.data_points:
163 f.absdelay = f.delay - min_delay
164
165 stream_duration_sender = self.data_points[-1].real_send_time_ms / 1000
166 print("Stream duration at sender: {:.1f} seconds".format(
167 stream_duration_sender
168 ))
169
170 arrival_timestamps_ms = [pt.arrival_timestamp_ms for pt in
171 self.data_points]
172 stream_duration_receiver = (max(arrival_timestamps_ms) -
173 min(arrival_timestamps_ms)) / 1000
174 print("Stream duration at receiver: {:.1f} seconds".format(
175 stream_duration_receiver
176 ))
177
178 print("Clock drift: {:.2f}%".format(
179 100 * (stream_duration_receiver / stream_duration_sender - 1)
180 ))
181
182 total_size = sum(x.size for x in self.data_points) * 8 / 1000
183 print("Send average bitrate: {:.2f} kbps".format(
184 total_size / stream_duration_sender))
185
186 print("Receive average bitrate: {:.2f} kbps".format(
187 total_size / stream_duration_receiver))
188
189 def remove_reordered(self):
190 last = self.data_points[0]
191 data_points_ordered = [last]
192 for x in self.data_points[1:]:
193 if x.sequence_number > last.sequence_number and (x.real_send_time_ms >
194 last.real_send_time_ms):
195 data_points_ordered.append(x)
196 last = x
197 self.data_points = data_points_ordered
198
199 def compute_bandwidth(self):
200 """Computes bandwidth averaged over several consecutive packets.
201
202 The number of consecutive packets used in the average is
203 BANDWIDTH_SMOOTHING_WINDOW_SIZE. Averaging is done with
204 numpy.correlate.
205 """
206 self.bandwidth_kbps = []
207 for i in range(len(self.data_points) - 1):
208 self.bandwidth_kbps.append(self.data_points[i].size * 8 /
209 (self.data_points[i +
210 1].real_send_time_ms -
211 self.data_points[i].real_send_time_ms)
212 )
213 correlate_filter = (numpy.ones(
214 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE) /
215 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE)
216 self.smooth_bw_kbps = numpy.correlate(self.bandwidth_kbps, correlate_filter)
217
218 def plot_statistics(self):
219 """Plots changes in delay and average bandwidth."""
220 plt.figure(1)
221 plt.plot([f.real_send_time_ms / 1000 for f in self.data_points],
222 [f.absdelay for f in self.data_points])
223 plt.xlabel("Send time [s]")
224 plt.ylabel("Relative transport delay [ms]")
225
226 plt.figure(2)
227 plt.plot([f.real_send_time_ms / 1000 for f in
228 self.data_points][:len(self.smooth_bw_kbps)],
229 self.smooth_bw_kbps[:len(self.data_points)])
230 plt.xlabel("Send time [s]")
231 plt.ylabel("Bandwidth [kbps]")
232
233 plt.show()
234
235
236 def main():
237
238 if len(sys.argv) < 2:
239 print("Usage: python rtp_analyzer.py <filename of rtc event log>")
240 sys.exit(0)
241
242 data_points = pb_parse.parse_protobuf(sys.argv[1])
243 rtp_stats = RTPStatistics(data_points)
244 chosen_ssrc = rtp_stats.choose_ssrc()
245 print("Chosen SSRC: 0X{:X}".format(chosen_ssrc))
246
247 rtp_stats.filter_ssrc(chosen_ssrc)
248 print("Statistics:")
249 rtp_stats.print_sequence_number_statistics()
250 rtp_stats.estimate_frequency()
251 rtp_stats.print_duration_statistics()
252 rtp_stats.remove_reordered()
253 rtp_stats.compute_bandwidth()
254 rtp_stats.plot_statistics()
255
256 if __name__ == "__main__":
257 main()
OLDNEW
« tools/py_event_log_analyzer/misc.py ('K') | « tools/py_event_log_analyzer/pb_parse.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698