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

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: Fraction dictionaries and other small fixes 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,
kwiberg-webrtc 2016/05/31 09:04:47 Spaces around *
aleloi 2016/05/31 09:49:48 Added whitespace here and in rest of CL. Exponenti
kwiberg-webrtc 2016/05/31 10:41:35 OK. A bit inconsistent to have one binary operator
61 self.ssrc_size_table[ssrc] * 100))
62 print(" packet sizes:")
63 bin_counts, bin_bounds = numpy.histogram([x.size for x in
kwiberg-webrtc 2016/05/31 09:04:48 (bin_counts, bin_bounds) = as you have done in ot
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):
kwiberg-webrtc 2016/05/31 09:04:47 Parenthesis around the tuple here too.
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: {}".format(fs_est))
145 if fs is None:
146 fs = int(misc.get_input(
147 "Frequency could not be guessed. Input frequency> "))
148 else:
149 print("Guessed frequency: {}".format(fs))
kwiberg-webrtc 2016/05/31 09:04:48 In all three messages, it'll probably be useful to
aleloi 2016/05/31 09:49:48 Yes, in particular in the input query.
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 print("Send average bitrate: {:.2f} kbps".format(
183 sum(x.size for x
184 in self.data_points) * 8 / stream_duration_sender / 1000))
185
186 print("Receive average bitrate: {:.2f} kbps".format(
187 sum(x.size
188 for x in self.data_points) * 8 / stream_duration_receiver /
189 1000))
kwiberg-webrtc 2016/05/31 09:04:47 You can extract a substantial common subexpression
aleloi 2016/05/31 09:49:48 Done.
190
191 def remove_reordered(self):
192 last = self.data_points[0]
193 data_points_ordered = [last]
194 for x in self.data_points[1:]:
195 if x.sequence_number > last.sequence_number and (x.real_send_time_ms >
196 last.real_send_time_ms):
197 data_points_ordered.append(x)
198 last = x
199 self.data_points = data_points_ordered
200
201 def compute_bandwidth(self):
202 """Computes bandwidth averaged over several consecutive packets.
203
204 The number of consecutive packets used in the average is
205 BANDWIDTH_SMOOTHING_WINDOW_SIZE. Averaging is done with
206 numpy.correlate.
207 """
208 self.bandwidth_kbps = []
209 for i in range(len(self.data_points)-1):
kwiberg-webrtc 2016/05/31 09:04:48 Spaces around -
210 self.bandwidth_kbps.append(
211 self.data_points[i].size*8 / (self.data_points[i+1].real_send_time_ms
kwiberg-webrtc 2016/05/31 09:04:47 Spaces around * and +
212 - self.data_points[i].real_send_time_ms)
213 )
214 correlate_filter = (numpy.ones(
215 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE) /
216 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE)
217 self.smooth_bw_kbps = numpy.correlate(self.bandwidth_kbps, correlate_filter)
218
219 def plot_statistics(self):
220 """Plots changes in delay and average bandwidth."""
221 plt.figure(1)
222 plt.plot([f.real_send_time_ms/1000 for f in self.data_points],
kwiberg-webrtc 2016/05/31 09:04:47 Spaces around /
223 [f.absdelay for f in self.data_points])
224 plt.xlabel("Send time [s]")
225 plt.ylabel("Relative transport delay [ms]")
226
227 plt.figure(2)
228 plt.plot([f.real_send_time_ms / 1000 for f in
229 self.data_points][:len(self.smooth_bw_kbps)],
230 self.smooth_bw_kbps[:len(self.data_points)])
231 plt.xlabel("Send time [s]")
232 plt.ylabel("Bandwidth [kbps]")
233
234 plt.show()
235
236
237 def main():
238
239 if len(sys.argv) < 2:
240 print("Usage: python rtp_analyzer.py <filename of rtc event log>")
241 sys.exit(0)
242
243 data_points = pb_parse.parse_protobuf(sys.argv[1])
244 rtp_stats = RTPStatistics(data_points)
245 chosen_ssrc = rtp_stats.choose_ssrc()
246 print("Chosen SSRC: 0X{:X}".format(chosen_ssrc))
247
248 rtp_stats.filter_ssrc(chosen_ssrc)
249 print("Statistics:")
250 rtp_stats.print_sequence_number_statistics()
251 rtp_stats.estimate_frequency()
252 rtp_stats.print_duration_statistics()
253 rtp_stats.remove_reordered()
254 rtp_stats.compute_bandwidth()
255 rtp_stats.plot_statistics()
256
257 if __name__ == "__main__":
258 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