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

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: Updated README to reflect changes in code. Created 4 years, 7 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 builtins
17 import matplotlib.pyplot as plt
18 import misc
19 import numpy
20 import pb_parse
21
22
23
24 class RTPStatistics(object):
25 """Has methods for calculating and plotting statistics for RPT
hlundin-webrtc 2016/05/25 12:54:23 RPT -> RTP
26 packets.
27 """
28
29 BANDWIDTH_SMOOTHING_WINDOW_SIZE = 10
30
31 def __init__(self, data_points):
32 """Initializes object with data_points computes simple statistics:
kwiberg-webrtc 2016/05/25 12:42:42 and
aleloi2 2016/05/30 14:57:56 Done.
33 percentages of packets and packet sizes by SSRC.
34
35 Args:
36 data_points: list of pb_parse.DataPoints on which statistics are
37 calculated.
38
39 """
40
41 self.data_points = data_points
42 self.ssrc_frequencies = misc.percent_table([x.ssrc for x in
43 self.data_points])
44 self.ssrc_size_table = misc.ssrc_size_table(self.data_points)
45 self.bandwidth_kbps = None
46 self.smooth_bw_kbps = None
47
48 def print_ssrc_info(self, ssrc_id, ssrc):
49 """Prints packet and size statistics for given SSRC.
peah-webrtc 2016/05/26 06:44:18 Maybe "the" or "a" should be added between "for" a
aleloi2 2016/05/30 14:57:56 Done.
50
51 Args:
52 ssrc_id: textual identifier of SSRC printed beside statistics for it.
53 ssrc: SSRC by which to filter data and display statistics
54
55 Raises:
56 Exception: when different payload types are present in data
hlundin-webrtc 2016/05/25 12:54:23 This will happen, and can be legit. When comfort n
aleloi2 2016/05/30 14:57:55 Now it prints the different payload types instead
57 for same SSRC
58 """
59 filtered_ssrc = [x for x in self.data_points if x.ssrc == ssrc]
60 payloads = misc.percent_table([x.payload_type for x in filtered_ssrc])
61
62 if len(payloads) == 1:
63 payload_info = "payload type {}".format(*list(payloads))
64 else:
65 raise Exception(
66 "This tool cannot yet handle changes in codec sample rate")
67 print("{} 0X{:X} {}, {:.2f}% packets, {:.2f}% data".format(
kwiberg-webrtc 2016/05/25 12:42:42 Consider using lowercase hexadecimal. It causes le
aleloi2 2016/05/30 14:57:56 Ok.
68 ssrc_id, ssrc, payload_info, self.ssrc_frequencies[ssrc]*100,
69 self.ssrc_size_table[ssrc]*100))
kwiberg-webrtc 2016/05/25 12:42:41 You still have some binary operators not surrounde
aleloi2 2016/05/30 14:57:56 Done.
70 print(" packet sizes:")
71 bin_counts, bin_bounds = numpy.histogram([x.size for x in
72 filtered_ssrc], bins=5,
73 density=False)
74 bin_proportions = bin_counts / sum(bin_counts)
75 print("\n".join([
76 " {:.1f} - {:.1f}: {:.2f}%".format(bin_bounds[i], bin_bounds[i+1],
77 bin_proportions[i]*100)
78 for i in builtins.range(len(bin_proportions))
79 ]))
80
81 def choose_ssrc(self):
82 """Queries user for SSRC."""
83 ssrc_frequencies_lst = list(enumerate(self.ssrc_frequencies))
kwiberg-webrtc 2016/05/25 12:42:42 Eliminate this variable. You don't use it for anyt
aleloi2 2016/05/30 14:57:56 It was used to pair ssrc with an integer counter,
84
85 if len(self.ssrc_frequencies) == 1:
86 chosen_ssrc = self.ssrc_frequencies[0][-1]
87 self.print_ssrc_info("", chosen_ssrc)
88 return chosen_ssrc
89
90 for i, ssrc in enumerate(self.ssrc_frequencies):
91 self.print_ssrc_info(i, ssrc)
92
93 while True:
94 chosen_index = int(builtins.input("choose one> "))
95 if 0 <= chosen_index < len(ssrc_frequencies_lst):
96 return ssrc_frequencies_lst[chosen_index][-1]
97 else:
98 print("Invalid index!")
99
100 def filter_ssrc(self, chosen_ssrc):
101 """Filters and wraps data points.
102
103 Removes data points with `ssrc != chosen_ssrc`. Unwraps sequence
104 numbers and time stamps for the chosen selection.
hlundin-webrtc 2016/05/25 12:54:23 Nit: 'timestamps' is typically written as one word
aleloi2 2016/05/30 14:57:56 Done.
105 """
106 self.data_points = [x for x in self.data_points if x.ssrc ==
107 chosen_ssrc]
108 unwrapped_sequence_numbers = misc.unwrap([x.sequence_number for x in
109 self.data_points],
110 2**16-1)
111 for (data_point, sequence_number) in zip(self.data_points,
112 unwrapped_sequence_numbers):
113 data_point.sequence_number = sequence_number
114
115 unwrapped_timestamps = misc.unwrap([x.timestamp for x in self.data_points],
116 2**32-1)
117
118 for (data_point, timestamp) in zip(self.data_points,
119 unwrapped_timestamps):
120 data_point.timestamp = timestamp
121
122 def print_sequence_number_statistics(self):
123 seq_no_set = set(x.sequence_number for x in self.data_points)
124 print("Missing sequence numbers: {} out of {}".format(
125 max(seq_no_set) - min(seq_no_set) + 1 - len(seq_no_set),
126 len(seq_no_set)
127 ))
128 packet_counter = collections.Counter(x.sequence_number for x in
129 self.data_points)
130 print("Duplicated packets: {}".format(sum(packet_counter.values())
131 - len(packet_counter)
132 ))
kwiberg-webrtc 2016/05/25 12:42:41 Unless I'm mistaken, it's simpler and more efficie
aleloi2 2016/05/30 14:57:56 I agree. Changed.
133 print("Reordered packets: {}".format(
134 misc.count_reordered([x.sequence_number for x in self.data_points])))
135
136 def print_frequency_duration_statistics(self):
peah-webrtc 2016/05/26 06:44:19 Suggestion: This is a fairly long method which doe
aleloi2 2016/05/30 14:57:56 I agree. I divided it into a frequency related and
137 """Estimates frequency and prints related statistics.
138
139 Guesses the most probable frequency by looking at changes in
140 timestamps (RFC 3550 section 5.1), calculates clock drifts and
141 sending time of packets. Updates `self.data_points` with changes
142 in delay and send time.
143
144 """
145 delta_timestamp = (self.data_points[-1].timestamp -
146 self.data_points[0].timestamp)
147 delta_arr_timestamp = float((self.data_points[-1].arrival_timestamp_ms -
148 self.data_points[0].arrival_timestamp_ms))
149 fs_est = delta_timestamp / delta_arr_timestamp
150
151 fs_vec = [8, 16, 32, 48, 90] # TODO(aleloi) 90 is a hack for video
hlundin-webrtc 2016/05/25 12:54:23 Not really a "hack"; https://tools.ietf.org/html/r
peah-webrtc 2016/05/26 06:44:18 The python style guide actually has an example whe
152 fs = None
153 for f in fs_vec:
154 if abs((fs_est - f)/float(f)) < 0.05:
kwiberg-webrtc 2016/05/25 12:42:42 No need for float, since you import division from
aleloi2 2016/05/30 14:57:56 OK.
155 fs = f
156
157 print("Estimated frequency: {}".format(fs_est))
158 if fs is None:
159 fs = int(builtins.input(
160 "Frequency could not be guessed. Input frequency> "))
161
162 print("Guessed frequency: {}".format(fs))
kwiberg-webrtc 2016/05/25 12:42:41 Don't print this if the user entered a frequency.
aleloi2 2016/05/30 14:57:56 Done.
163
164 for f in self.data_points:
165 f.real_send_time_ms = (f.timestamp -
166 self.data_points[0].timestamp) / fs
167 f.delay = f.arrival_timestamp_ms - f.real_send_time_ms
168
169 min_delay = min(f.delay for f in self.data_points)
170
171 for f in self.data_points:
hlundin-webrtc 2016/05/25 12:54:23 Oh. Python cannot do "vector +/- scalar" without e
kwiberg-webrtc 2016/05/25 13:00:31 No. list + list returns a new list that's a concat
ivoc 2016/05/25 13:17:37 It could do it if it was a numpy array instead of
kwiberg-webrtc 2016/05/25 13:29:04 Right, I'd forgotten about numpy, because I never
172 f.absdelay = f.delay - min_delay
173
174 stream_duration_sender = self.data_points[-1].real_send_time_ms / 1000
175 print("Stream duration at sender: {:.1f} seconds".format(
176 stream_duration_sender
177 ))
178
179 arrival_timestamps_ms = [pt.arrival_timestamp_ms for pt in
180 self.data_points]
181 stream_duration_receiver = (max(arrival_timestamps_ms) -
182 min(arrival_timestamps_ms)) / 1000
183 print("Stream duration at receiver: {:.1f} seconds".format(
184 stream_duration_receiver
185 ))
186
187 print("Clock drift: {:.2f}%".format(
188 100* (stream_duration_receiver / stream_duration_sender - 1)
189 ))
190
191 print("Send average bitrate: {:.2f} kbps".format(
192 sum(x.size for x
193 in self.data_points) * 8 / stream_duration_sender / 1000))
194
195 print("Receive average bitrate: {:.2f} kbps".format(
196 sum(x.size
197 for x in self.data_points) * 8 / stream_duration_receiver /
198 1000))
199
200 def remove_reordered(self):
201 last = self.data_points[0]
202 data_points_ordered = [last]
203 for x in self.data_points[1:]:
204 if x.sequence_number > last.sequence_number and (x.real_send_time_ms >
205 last.real_send_time_ms):
206 data_points_ordered.append(x)
207 last = x
208 self.data_points = data_points_ordered
209
210 def compute_bandwidth(self):
211 """Computes bandwidth averaged over several consecutive packets.
212
213 The number of consecutive packets used in the average is
214 BANDWIDTH_SMOOTHING_WINDOW_SIZE. Averaging is done with
215 numpy.correlate.
216 """
217 self.bandwidth_kbps = []
218 for i in range(len(self.data_points)-1):
219 self.bandwidth_kbps.append(
220 self.data_points[i].size*8 / (self.data_points[i+1].real_send_time_ms
221 - self.data_points[i].real_send_time_ms)
222 )
223 correlate_filter = (numpy.ones(
224 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE) /
225 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE)
226 self.smooth_bw_kbps = numpy.correlate(self.bandwidth_kbps, correlate_filter)
227
228 def plot_statistics(self):
229 """Plots changes in delay and average bandwidth."""
230 plt.figure(1)
231 plt.plot([f.real_send_time_ms/1000 for f in self.data_points],
232 [f.absdelay for f in self.data_points])
233 plt.xlabel("Send time [s]")
234 plt.ylabel("Relative transport delay [ms]")
235
236 plt.figure(2)
237 plt.plot([f.real_send_time_ms / 1000 for f in
238 self.data_points][:len(self.smooth_bw_kbps)],
239 self.smooth_bw_kbps[:len(self.data_points)])
240 plt.xlabel("Send time [s]")
241 plt.ylabel("Bandwidth [kbps]")
242
243 plt.show()
244
245
246 def main():
247
248 if len(sys.argv) < 2:
249 print("Usage: python rtp_analyzer.py <filename of rtc event log>")
250 sys.exit(0)
251
252 data_points = pb_parse.parse_protobuf(sys.argv[1])
253 rtp_stats = RTPStatistics(data_points)
254 chosen_ssrc = rtp_stats.choose_ssrc()
255 print("Chosen SSRC: 0X{:X}".format(chosen_ssrc))
256
257 rtp_stats.filter_ssrc(chosen_ssrc)
258 print("Statistics:")
259 rtp_stats.print_sequence_number_statistics()
260 rtp_stats.print_frequency_duration_statistics()
261 rtp_stats.remove_reordered()
262 rtp_stats.compute_bandwidth()
263 rtp_stats.plot_statistics()
264
265 if __name__ == "__main__":
266 main()
OLDNEW
« tools/py_event_log_analyzer/pb_parse.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