-
Notifications
You must be signed in to change notification settings - Fork 996
/
Copy pathtimeseries-cli.py
executable file
·203 lines (152 loc) · 7.62 KB
/
timeseries-cli.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#!/usr/bin/env python
import argparse
import pandas as pd
import matplotlib.pyplot as plt
from pandas.plotting import register_matplotlib_converters
register_matplotlib_converters()
def read_json(fname):
df = pd.read_json(fname, lines=True, convert_dates=['time'], date_unit='ms')
df['conference_id'] = df['conf_name'] + df['conf_creation_time_ms'].astype(str)
return df
def show(args):
df = read_json(args.infile)
print('series: {}'.format(df['series'].unique()))
print('conferences: {}'.format(df['conf_name'].unique()))
print('endpoints: {}'.format(df['endpoint_id'].unique()))
def vp8_inspect(df):
if args.ssrc:
df = df[df['rtp.ssrc'] == args.ssrc]
if args.endpoint:
df = df[df['endpoint_id'] == args.endpoint]
if args.conference:
df = df[df['conf_name'] == args.conference]
df['time_delta'] = df['time'] - df['time'].shift(1)
df['rtp.seq_delta'] = df['rtp.seq'] - df['rtp.seq'].shift(1)
df['vp8.pictureid_delta'] = df['vp8.pictureid'] - df['vp8.pictureid'].shift(1)
df['vp8.timestamp_delta'] = df['rtp.timestamp'] - df['rtp.timestamp'].shift(1)
print(df)
def vp8_verify(df):
# tl0picidx monotonically increases
# if the timestamp changes, then the pictureid changes
# if the pictureid changes, then the timestamp changes
# the timestamp delta needs to be proportional to the pictureid delta
# there can't be big gaps in the sequence numbers
# the webrtc pacer outputs packets every 10ms
pass
def plot_endpoint(df, series, endpoint_id, remote_endpoint_id):
df = df[df['endpoint_id'] == endpoint_id]
if remote_endpoint_id:
df = df[df['remote_endpoint_id'] == remote_endpoint_id]
fig = plt.figure()
fig.suptitle('Endpoint: {}'.format(endpoint_id))
ax_bitrate = fig.subplots(1, sharex=True)
ax_bitrate.set_xlabel('time')
ax_bitrate.set_ylabel('bitrate (bps)')
if 'calculated_rate' in series:
df_rates = df['series' == 'calculated_rate']
# make sure we're plotting a single remote endpoint.
remote_endpoints = df_rates['remote_endpoint_id'].unique()
if len(remote_endpoints) > 1:
raise Exception('specify a --remote-endpoint {}'.format(remote_endpoints))
for i in range(9):
encoding_quality = str(i)
if encoding_quality in df_rates.columns:
ax_bitrate.plot(df_rates['time'],
df_rates[encoding_quality],
label=encoding_quality)
df_series = df['series'].unique()
if 'sent_padding' in series and 'sent_padding' in df_series:
df_padding = df[df['series'] == 'sent_padding']
ax_bitrate.plot(
df_padding['time'],
df_padding['padding_bps'] + df_padding['total_target_bps'],
label='target + padding',
marker='^',
markersize=4,
linestyle='None')
if 'new_bwe' in series and 'new_bwe' in df_series:
df_bwe = df[df['series'] == 'new_bwe']
ax_bitrate.plot(df_bwe['time'],
df_bwe['bitrate_bps'],
label='estimate',
drawstyle='steps-post')
if 'did_update' in series and 'did_update' in df_series:
df_update = df[df['series'] == 'did_update']
ax_bitrate.plot(df_update['time'],
df_update['total_target_bps'],
label='target',
drawstyle='steps-post')
ax_bitrate.plot(df_update['time'],
df_update['total_ideal_bps'],
label='ideal',
drawstyle='steps-post')
if 'in_pkt' in series and 'in_pkt' in df_series:
df_pkt = df[df['series'] == 'in_pkt']
if len(df_pkt['rbe_id'].unique()) is not 1:
raise Exception('There cannot be multiple remote bitrate estimators')
df_sz = pd.DataFrame(
{'bits': df_pkt['pkt_sz_bytes'] * 8, 'time': df_pkt['time']})
df_rate = df_sz.resample('1S', on='time').sum()
ax_bitrate.plot(df_rate.index, df_rate['bits'], label='sent')
# todo include rtt and packet loss
ax_bitrate.legend()
def plot(args):
df = read_json(args.infile)
endpoint_ids = [args.endpoint_id] if args.endpoint_id else df['endpoint_id'].unique()
for endpoint_id in endpoint_ids:
plot_endpoint(df, args.series, endpoint_id, args.remote_endpoint_id)
plt.show()
def check_endpoint(df, endpoint_id):
# if a property is violated (for example property 3):
# zgrep 7d2b7ecf data/4/series.json.gz| grep did_update | jq '. | select(.bwe_bps >= .total_ideal_bps) | select(.total_target_idx < .total_ideal_idx)' | less -S
df_update = df[df['series'] == 'did_update']
if not len(df_update):
return
# property 1: check if ideal <= estimate => target == ideal
mask_1 = df_update['bwe_bps'] >= df_update['total_ideal_bps']
mask_2 = df_update['total_target_idx'] < df_update['total_ideal_idx']
mask_3 = df_update['total_ideal_bps'] != 0
mask_4 = df_update['total_target_bps'] != 0
assert not len(df_update[mask_1 & mask_2 & mask_3 & mask_4]), '{} is not sending as much as it should.'.format(endpoint_id)
# property 2: check that the target never exceeds the ideal
mask_5 = (df_update['total_ideal_bps'] - df_update['total_target_bps']) < 0
assert not len(df_update[mask_5]), '{} has a target bitrate that exceeds the ideal bitrate.'.format(endpoint_id)
# property 3: make sure that we are calculating bitrate for encodings
# disabled for now as it's a legitimate case, being video muted, for example
#mask_6 = df_update['total_ideal_bps'] > 0
#assert len(df_update[mask_6]), '{} never computed an ideal bitrate.'.format(endpoint_id)
#mask_7 = df_update['total_target_bps'] > 0
#assert len(df_update[mask_7]), '{} never computed a target bitrate.'.format(endpoint_id)
# property 4: check that the target never exceeds the estimate
# disabled for now as can happen and still be correct because we never suspend the on-stage participant
# mask_8 = (df_update['bwe_bps'] - df_update['total_target_bps']) < 0
# assert not len(df_update[mask_8]), '{} has a target bitrate that exceeds the bandwidth estimation.'.format(endpoint_id)
def check_conference(df, conference_id):
endpoint_ids = [args.endpoint_id] if args.endpoint_id else df['endpoint_id'].unique()
for endpoint_id in endpoint_ids:
check_endpoint(df[df['endpoint_id'] == endpoint_id], endpoint_id)
def check(args):
df = read_json(args.infile)
conference_ids = [args.conference_id] if args.conference_id else df['conference_id'].unique()
for conference_id in conference_ids:
check_conference(df[df['conference_id'] == conference_id], conference_id)
if "__main__" == __name__:
parser = argparse.ArgumentParser()
parser.add_argument('infile', type=argparse.FileType('r'))
subparsers = parser.add_subparsers()
parser_show = subparsers.add_parser('show')
parser_show.set_defaults(func=show)
parser_plot = subparsers.add_parser('plot')
parser_plot.add_argument(
'--series', nargs='+', choices=[
'did_update', 'new_bwe', 'sent_padding', 'calculated_rate'],
default='did_update new_bwe sent_padding in_pkt')
parser_plot.add_argument('--endpoint-id')
parser_plot.add_argument('--remote-endpoint-id')
parser_plot.set_defaults(func=plot)
parser_check = subparsers.add_parser('check')
parser_check.add_argument('--endpoint-id')
parser_check.add_argument('--conference-id')
parser_check.set_defaults(func=check)
args = parser.parse_args()
args.func(args)