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

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: Formatting, licence and usage string. 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 RTP stream statistics."""
24
25 BANDWIDTH_SMOOTHING_WINDOW_SIZE = 10
26
27 def __init__(self, data_points):
28 """Initializes object with data_points and computes simple statistics.
29
30 Computes percentages of number of packets and packet sizes by
31 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 = [point for point in self.data_points if point.ssrc
54 == ssrc]
55 payloads = misc.normalize_counter(
56 collections.Counter([point.payload_type for point in
57 filtered_ssrc]))
58
59 payload_info = "payload type(s): {}".format(
60 ", ".join(str(payload) for payload in payloads))
61 print("{} 0x{:x} {}, {:.2f}% packets, {:.2f}% data".format(
62 ssrc_id, ssrc, payload_info, self.ssrc_frequencies[ssrc] * 100,
63 self.ssrc_size_table[ssrc] * 100))
64 print(" packet sizes:")
65 (bin_counts, bin_bounds) = numpy.histogram([point.size for point in
66 filtered_ssrc], bins=5,
67 density=False)
68 bin_proportions = bin_counts / sum(bin_counts)
69 print("\n".join([
70 " {:.1f} - {:.1f}: {:.2f}%".format(bin_bounds[i], bin_bounds[i + 1],
71 bin_proportions[i] * 100)
72 for i in range(len(bin_proportions))
73 ]))
74
75 def choose_ssrc(self):
76 """Queries user for SSRC."""
77
78 if len(self.ssrc_frequencies) == 1:
79 chosen_ssrc = self.ssrc_frequencies[0][-1]
80 self.print_ssrc_info("", chosen_ssrc)
81 return chosen_ssrc
82
83 for (i, ssrc) in enumerate(self.ssrc_frequencies):
84 self.print_ssrc_info(i, ssrc)
85
86 while True:
87 chosen_index = int(misc.get_input("choose one> "))
88 if 0 <= chosen_index < len(self.ssrc_frequencies):
89 return list(self.ssrc_frequencies)[chosen_index]
90 else:
91 print("Invalid index!")
92
93 def filter_ssrc(self, chosen_ssrc):
94 """Filters and wraps data points.
95
96 Removes data points with `ssrc != chosen_ssrc`. Unwraps sequence
97 numbers and timestamps for the chosen selection.
98 """
99 self.data_points = [point for point in self.data_points if
100 point.ssrc == chosen_ssrc]
101 unwrapped_sequence_numbers = misc.unwrap(
102 [point.sequence_number for point in self.data_points], 2**16 - 1)
103 for (data_point, sequence_number) in zip(self.data_points,
104 unwrapped_sequence_numbers):
105 data_point.sequence_number = sequence_number
106
107 unwrapped_timestamps = misc.unwrap([point.timestamp for point in
108 self.data_points], 2**32 - 1)
109
110 for (data_point, timestamp) in zip(self.data_points,
111 unwrapped_timestamps):
112 data_point.timestamp = timestamp
113
114 def print_sequence_number_statistics(self):
115 seq_no_set = set(point.sequence_number for point in
116 self.data_points)
117 print("Missing sequence numbers: {} out of {}".format(
118 max(seq_no_set) - min(seq_no_set) + 1 - len(seq_no_set),
119 len(seq_no_set)
120 ))
121 print("Duplicated packets: {}".format(len(self.data_points) -
122 len(seq_no_set)))
123 print("Reordered packets: {}".format(
124 misc.count_reordered([point.sequence_number for point in
125 self.data_points])))
126
127 def estimate_frequency(self):
128 """Estimates frequency and updates data.
129
130 Guesses the most probable frequency by looking at changes in
131 timestamps (RFC 3550 section 5.1), calculates clock drifts and
132 sending time of packets. Updates `self.data_points` with changes
133 in delay and send time.
134 """
135 delta_timestamp = (self.data_points[-1].timestamp -
136 self.data_points[0].timestamp)
137 delta_arr_timestamp = float((self.data_points[-1].arrival_timestamp_ms -
138 self.data_points[0].arrival_timestamp_ms))
139 freq_est = delta_timestamp / delta_arr_timestamp
140
141 freq_vec = [8, 16, 32, 48, 90]
142 freq = None
143 for f in freq_vec:
144 if abs((freq_est - f) / f) < 0.05:
145 freq = f
146
147 print("Estimated frequency: {}kHz".format(freq_est))
148 if freq is None:
149 freq = int(misc.get_input(
150 "Frequency could not be guessed. Input frequency (in kHz)> "))
151 else:
152 print("Guessed frequency: {}kHz".format(freq))
153
154 for point in self.data_points:
155 point.real_send_time_ms = (point.timestamp -
156 self.data_points[0].timestamp) / freq
157 point.delay = point.arrival_timestamp_ms -point.real_send_time_ms
158
159 def print_duration_statistics(self):
160 """Prints delay, clock drift and bitrate statistics."""
161
162 min_delay = min(point.delay for point in self.data_points)
163
164 for point in self.data_points:
165 point.absdelay = point.delay - min_delay
166
167 stream_duration_sender = self.data_points[-1].real_send_time_ms / 1000
168 print("Stream duration at sender: {:.1f} seconds".format(
169 stream_duration_sender
170 ))
171
172 arrival_timestamps_ms = [point.arrival_timestamp_ms for point in
173 self.data_points]
174 stream_duration_receiver = (max(arrival_timestamps_ms) -
175 min(arrival_timestamps_ms)) / 1000
176 print("Stream duration at receiver: {:.1f} seconds".format(
177 stream_duration_receiver
178 ))
179
180 print("Clock drift: {:.2f}%".format(
181 100 * (stream_duration_receiver / stream_duration_sender - 1)
182 ))
183
184 total_size = sum(point.size for point in self.data_points) * 8 / 1000
185 print("Send average bitrate: {:.2f} kbps".format(
186 total_size / stream_duration_sender))
187
188 print("Receive average bitrate: {:.2f} kbps".format(
189 total_size / stream_duration_receiver))
190
191 def remove_reordered(self):
192 last = self.data_points[0]
193 data_points_ordered = [last]
194 for point in self.data_points[1:]:
195 if point.sequence_number > last.sequence_number and (
196 point.real_send_time_ms > last.real_send_time_ms):
197 data_points_ordered.append(point)
198 last = point
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):
210 self.bandwidth_kbps.append(self.data_points[i].size * 8 /
211 (self.data_points[i +
212 1].real_send_time_ms -
213 self.data_points[i].real_send_time_ms)
214 )
215 correlate_filter = (numpy.ones(
216 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE) /
217 RTPStatistics.BANDWIDTH_SMOOTHING_WINDOW_SIZE)
218 self.smooth_bw_kbps = numpy.correlate(self.bandwidth_kbps, correlate_filter)
219
220 def plot_statistics(self):
221 """Plots changes in delay and average bandwidth."""
222 plt.figure(1)
223 plt.plot([f.real_send_time_ms / 1000 for f in self.data_points],
224 [f.absdelay for f in self.data_points])
225 plt.xlabel("Send time [s]")
226 plt.ylabel("Relative transport delay [ms]")
227
228 plt.figure(2)
229 plt.plot([f.real_send_time_ms / 1000 for f in
230 self.data_points][:len(self.smooth_bw_kbps)],
231 self.smooth_bw_kbps[:len(self.data_points)])
232 plt.xlabel("Send time [s]")
233 plt.ylabel("Bandwidth [kbps]")
234
235 plt.show()
236
237
238 def main():
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

Powered by Google App Engine
This is Rietveld 408576698