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: webrtc/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: Added protobuf dependency back. 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
17 import matplotlib.pyplot as plt
18 import numpy
19
20 import misc
21 import pb_parse
22
23
24 class RTPStatistics(object):
25 """Has methods for calculating and plotting RTP stream statistics."""
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
32 Computes percentages of number of packets and packet sizes by
33 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.normalize_counter(
43 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.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 a given SSRC.
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 filtered_ssrc = [point for point in self.data_points if point.ssrc
56 == ssrc]
57 payloads = misc.normalize_counter(
58 collections.Counter([point.payload_type for point in
59 filtered_ssrc]))
60
61 payload_info = "payload type(s): {}".format(
62 ", ".join(str(payload) for payload in payloads))
63 print("{} 0x{:x} {}, {:.2f}% packets, {:.2f}% data".format(
64 ssrc_id, ssrc, payload_info, self.ssrc_frequencies[ssrc] * 100,
65 self.ssrc_size_table[ssrc] * 100))
66 print(" packet sizes:")
67 (bin_counts, bin_bounds) = numpy.histogram([point.size for point in
68 filtered_ssrc], bins=5,
69 density=False)
70 bin_proportions = bin_counts / sum(bin_counts)
71 print("\n".join([
72 " {:.1f} - {:.1f}: {:.2f}%".format(bin_bounds[i], bin_bounds[i + 1],
73 bin_proportions[i] * 100)
74 for i in range(len(bin_proportions))
75 ]))
76
77 def choose_ssrc(self):
78 """Queries user for SSRC."""
79
80 if len(self.ssrc_frequencies) == 1:
81 chosen_ssrc = self.ssrc_frequencies[0][-1]
82 self.print_ssrc_info("", chosen_ssrc)
83 return chosen_ssrc
84
85 for (i, ssrc) in enumerate(self.ssrc_frequencies):
86 self.print_ssrc_info(i, ssrc)
87
88 while True:
89 chosen_index = int(misc.get_input("choose one> "))
90 if 0 <= chosen_index < len(self.ssrc_frequencies):
91 return list(self.ssrc_frequencies)[chosen_index]
92 else:
93 print("Invalid index!")
94
95 def filter_ssrc(self, chosen_ssrc):
96 """Filters and wraps data points.
97
98 Removes data points with `ssrc != chosen_ssrc`. Unwraps sequence
99 numbers and timestamps for the chosen selection.
100 """
101 self.data_points = [point for point in self.data_points if
102 point.ssrc == chosen_ssrc]
103 unwrapped_sequence_numbers = misc.unwrap(
104 [point.sequence_number for point in self.data_points], 2**16 - 1)
105 for (data_point, sequence_number) in zip(self.data_points,
106 unwrapped_sequence_numbers):
107 data_point.sequence_number = sequence_number
108
109 unwrapped_timestamps = misc.unwrap([point.timestamp for point in
110 self.data_points], 2**32 - 1)
111
112 for (data_point, timestamp) in zip(self.data_points,
113 unwrapped_timestamps):
114 data_point.timestamp = timestamp
115
116 def print_sequence_number_statistics(self):
117 seq_no_set = set(point.sequence_number for point in
118 self.data_points)
119 print("Missing sequence numbers: {} out of {}".format(
120 max(seq_no_set) - min(seq_no_set) + 1 - len(seq_no_set),
121 len(seq_no_set)
122 ))
123 print("Duplicated packets: {}".format(len(self.data_points) -
124 len(seq_no_set)))
125 print("Reordered packets: {}".format(
126 misc.count_reordered([point.sequence_number for point in
127 self.data_points])))
128
129 def estimate_frequency(self):
130 """Estimates frequency and updates data.
131
132 Guesses the most probable frequency by looking at changes in
133 timestamps (RFC 3550 section 5.1), calculates clock drifts and
134 sending time of packets. Updates `self.data_points` with changes
135 in delay and send time.
136 """
137 delta_timestamp = (self.data_points[-1].timestamp -
138 self.data_points[0].timestamp)
139 delta_arr_timestamp = float((self.data_points[-1].arrival_timestamp_ms -
140 self.data_points[0].arrival_timestamp_ms))
141 freq_est = delta_timestamp / delta_arr_timestamp
142
143 freq_vec = [8, 16, 32, 48, 90]
144 freq = None
145 for f in freq_vec:
146 if abs((freq_est - f) / f) < 0.05:
147 freq = f
148
149 print("Estimated frequency: {}kHz".format(freq_est))
150 if freq is None:
151 freq = int(misc.get_input(
152 "Frequency could not be guessed. Input frequency (in kHz)> "))
153 else:
154 print("Guessed frequency: {}kHz".format(freq))
155
156 for point in self.data_points:
157 point.real_send_time_ms = (point.timestamp -
158 self.data_points[0].timestamp) / freq
159 point.delay = point.arrival_timestamp_ms -point.real_send_time_ms
160
161 def print_duration_statistics(self):
162 """Prints delay, clock drift and bitrate statistics."""
163
164 min_delay = min(point.delay for point in self.data_points)
165
166 for point in self.data_points:
167 point.absdelay = point.delay - min_delay
168
169 stream_duration_sender = self.data_points[-1].real_send_time_ms / 1000
170 print("Stream duration at sender: {:.1f} seconds".format(
171 stream_duration_sender
172 ))
173
174 arrival_timestamps_ms = [point.arrival_timestamp_ms for point in
175 self.data_points]
176 stream_duration_receiver = (max(arrival_timestamps_ms) -
177 min(arrival_timestamps_ms)) / 1000
178 print("Stream duration at receiver: {:.1f} seconds".format(
179 stream_duration_receiver
180 ))
181
182 print("Clock drift: {:.2f}%".format(
183 100 * (stream_duration_receiver / stream_duration_sender - 1)
184 ))
185
186 total_size = sum(point.size for point in self.data_points) * 8 / 1000
187 print("Send average bitrate: {:.2f} kbps".format(
188 total_size / stream_duration_sender))
189
190 print("Receive average bitrate: {:.2f} kbps".format(
191 total_size / stream_duration_receiver))
192
193 def remove_reordered(self):
194 last = self.data_points[0]
195 data_points_ordered = [last]
196 for point in self.data_points[1:]:
197 if point.sequence_number > last.sequence_number and (
198 point.real_send_time_ms > last.real_send_time_ms):
199 data_points_ordered.append(point)
200 last = point
201 self.data_points = data_points_ordered
202
203 def compute_bandwidth(self):
204 """Computes bandwidth averaged over several consecutive packets.
205
206 The number of consecutive packets used in the average is
207 BANDWIDTH_SMOOTHING_WINDOW_SIZE. Averaging is done with
208 numpy.correlate.
209 """
210 self.bandwidth_kbps = []
211 for i in range(len(self.data_points) - 1):
212 self.bandwidth_kbps.append(self.data_points[i].size * 8 /
213 (self.data_points[i +
214 1].real_send_time_ms -
215 self.data_points[i].real_send_time_ms)
216 )
217 correlate_filter = (numpy.ones(
218 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE) /
219 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE)
220 self.smooth_bw_kbps = numpy.correlate(self.bandwidth_kbps, correlate_filter)
221
222 def plot_statistics(self):
223 """Plots changes in delay and average bandwidth."""
224 plt.figure(1)
225 plt.plot([f.real_send_time_ms / 1000 for f in self.data_points],
226 [f.absdelay for f in self.data_points])
227 plt.xlabel("Send time [s]")
228 plt.ylabel("Relative transport delay [ms]")
229
230 plt.figure(2)
231 plt.plot([f.real_send_time_ms / 1000 for f in
232 self.data_points][:len(self.smooth_bw_kbps)],
233 self.smooth_bw_kbps[:len(self.data_points)])
234 plt.xlabel("Send time [s]")
235 plt.ylabel("Bandwidth [kbps]")
236
237 plt.show()
238
239
240 def main():
241 if len(sys.argv) < 2:
242 print("Usage: python rtp_analyzer.py <filename of rtc event log>")
243 sys.exit(0)
244
245 data_points = pb_parse.parse_protobuf(sys.argv[1])
246 rtp_stats = RTPStatistics(data_points)
247 chosen_ssrc = rtp_stats.choose_ssrc()
248 print("Chosen SSRC: 0X{:X}".format(chosen_ssrc))
249
250 rtp_stats.filter_ssrc(chosen_ssrc)
251 print("Statistics:")
252 rtp_stats.print_sequence_number_statistics()
253 rtp_stats.estimate_frequency()
254 rtp_stats.print_duration_statistics()
255 rtp_stats.remove_reordered()
256 rtp_stats.compute_bandwidth()
257 rtp_stats.plot_statistics()
258
259 if __name__ == "__main__":
260 main()
OLDNEW
« no previous file with comments | « webrtc/tools/py_event_log_analyzer/pb_parse.py ('k') | webrtc/tools/py_event_log_analyzer/rtp_analyzer.sh » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698