├── requirements.txt ├── img ├── packets_1.png ├── packets_2.png ├── sending_rate_mbps.png └── available_snd_buffer_size.png ├── .gitignore ├── README.md └── plot_srt_stats.py /requirements.txt: -------------------------------------------------------------------------------- 1 | bokeh>=2.0.1 2 | click>=7.0 3 | pandas>=1.0.3 -------------------------------------------------------------------------------- /img/packets_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbakholdina/srt-stats-plotting/HEAD/img/packets_1.png -------------------------------------------------------------------------------- /img/packets_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbakholdina/srt-stats-plotting/HEAD/img/packets_2.png -------------------------------------------------------------------------------- /img/sending_rate_mbps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbakholdina/srt-stats-plotting/HEAD/img/sending_rate_mbps.png -------------------------------------------------------------------------------- /img/available_snd_buffer_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbakholdina/srt-stats-plotting/HEAD/img/available_snd_buffer_size.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # srt-stats-plotting 2 | 3 |
6 | 7 | Script designed to plot graphs out of SRT core statistics. 8 | 9 | [SRT](https://github.com/Haivision/srt) stands for Secure Reliable Transport and is an open source transport technology that optimizes streaming performance across unpredictable networks, such as the Internet. 10 | 11 | ## Requirements 12 | 13 | * python 3.6+ 14 | * PhantomJS 2.1 optional to [export plots](https://bokeh.pydata.org/en/latest/docs/user_guide/export.html) 15 | 16 | To install the library dependencies run: 17 | ``` 18 | pip install -r requirements.txt 19 | ``` 20 | 21 | ## Script Usage 22 | 23 | The main purpose of `plot_srt_stats.py` script is to visualize SRT core statistics produced during experiments by one of the testing applications like [srt-xtransmit](https://github.com/maxsharabayko/srt-xtransmit) and [srt-live-transmit](https://github.com/Haivision/srt/blob/master/docs/apps/srt-live-transmit.md) or third-party solutions supporting SRT protocol. Depending on whether this statistics is collected on sender or receiver side, the data plotted may vary. 24 | 25 | SRT core statistics should be collected in a `.csv` file. Usually, as a naming convention rule the file name ends either on "-snd" or "-rcv" depending on the side (sender or receiver) where the data was collected. Or, file name can just contain "snd" or "rcv" part in it. 26 | 27 | Statistics filepath is passed as an argument to a script. Script usage 28 | ``` 29 | plot_srt_stats.py [OPTIONS] STATS_FILEPATH 30 | ``` 31 | 32 | Use `--help` option in order to get the full list of options 33 | ``` 34 | --is-sender Should be set if sender statistics is provided. Otherwise, it 35 | is assumed that receiver statistics is provided. 36 | --is-fec Should be set if packet filter (FEC) stats is enabled. 37 | --export-png Export plots to .png files. 38 | --help Show this message and exit. 39 | ``` 40 | 41 | ### Usage with srt-live-transmit 42 | For the usage with [srt-live-transmit](https://github.com/Haivision/srt/blob/master/docs/apps/srt-live-transmit.md), make sure that the export of SRT statistics is enabled in .csv format. This can be done by passing the following parameters: 43 | - ` -statspf:csv` to ensure the CSV format is used when exporting. 44 | - ` -statsout:/path/to/desired/folder/filename-rcv.csv` to define where to write the statistics 45 | - ` -stats-report-frequency:1000` to define how often to write statistics (per # of packets), this example sets it to once per thousand packets. 46 | 47 | More information on srt-live-transmit's command line parameters can be found [here](https://github.com/Haivision/srt/blob/master/docs/apps/srt-live-transmit.md#command-line-options). 48 | 49 | ## Plots Description 50 | 51 | **Note:** Megabytes and Rate plots correlation is determined by the following formula 52 | 53 | _Rate (Mbps) = MB / interval (s),_ 54 | 55 | where _interval_ is the interval indicating how frequently the statistics is collected (in seconds). 56 | 57 | 58 | ## Example Plots 59 | 60 | The script plots different charts from SRT statistics. Examples of `.csv` files and corresponding `.html` output can be found in the [examples](https://github.com/mbakholdina/srt-stats-plotting/tree/master/examples) folder. For example, a chart to visualize statistics on the packets being sent, lost, retransmitted, dropped or on flight: 61 | 62 |
63 |
64 | The legends are interactive. You can click, e.g., on `Dropped` and `On Flight` to disable the respective plots:
65 |
66 |
67 |
68 | Another chart illustrates the sending rate in Mbps:
69 |
70 |
71 |
72 | Available size of the sender's buffer helps to detect if there is enough space to store outgoing data, or the source generates data faster then SRT can transmit:
73 |
74 |
75 |
--------------------------------------------------------------------------------
/plot_srt_stats.py:
--------------------------------------------------------------------------------
1 | from collections import namedtuple
2 | import os
3 | import pathlib
4 |
5 | from bokeh import __version__ as bokeh_version
6 | import bokeh.io
7 | import bokeh.layouts as layouts
8 | import bokeh.models as models
9 | import bokeh.plotting as plotting
10 | import click
11 | import pandas as pd
12 |
13 |
14 | PLOT_WIDTH = 700
15 | PLOT_HEIGHT = 300
16 | TOOLS = 'pan,xwheel_pan,box_zoom,reset,save'
17 | linedesc = namedtuple("linedesc", ['col', 'legend', 'color'])
18 |
19 |
20 | class IsNotCSVFile(Exception):
21 | pass
22 |
23 |
24 | def export_plot_png(export_png, plot, name, postfix):
25 | if export_png:
26 | # The following two lines remove toolbar from PNG
27 | plot.toolbar.logo = None
28 | plot.toolbar_location = None
29 | bokeh.io.export_png(plot, filename=f'{name}-{postfix}.png')
30 |
31 |
32 | def create_figure(plot_width, plot_height, tools):
33 |
34 | # Backward compatibility for Bokeh
35 | if int(bokeh_version.split(".")[0]) >= 3:
36 | return plotting.figure(
37 | width=plot_width,
38 | height=plot_height,
39 | tools=tools
40 | )
41 | else:
42 | return plotting.figure(
43 | plot_width=plot_width,
44 | plot_height=plot_height,
45 | tools=tools
46 | )
47 |
48 | def create_plot(title, xlabel, ylabel, source, lines, yformatter=None):
49 |
50 | fig = create_figure(PLOT_WIDTH, PLOT_HEIGHT, TOOLS)
51 | fig.title.text = title
52 | fig.xaxis.axis_label = xlabel
53 | fig.yaxis.axis_label = ylabel
54 |
55 | fig.xaxis.formatter = models.NumeralTickFormatter(format='0,0')
56 | if yformatter is not None:
57 | fig.yaxis.formatter = yformatter
58 |
59 | is_legend = False
60 | for x in lines:
61 | if x.legend != '':
62 | is_legend = True
63 | fig.line(x='sTTime', y=x.col, color=x.color, legend_label=x.legend, source=source)
64 | else:
65 | fig.line(x='sTTime', y=x.col, color=x.color, source=source)
66 |
67 | if is_legend:
68 | fig.legend.click_policy="hide"
69 |
70 | return fig
71 |
72 |
73 | def create_packets_plot(source, is_sender):
74 | side_name = 'Sender' if is_sender else 'Receiver'
75 |
76 | if is_sender:
77 | lines = [
78 | linedesc('pktSent', 'Sent', 'green'),
79 | linedesc('pktSndLoss', 'Lost', 'orange'),
80 | linedesc('pktRetrans', 'Retransmitted', 'blue'),
81 | linedesc('pktSndDrop', 'Dropped', 'red'),
82 | linedesc('pktFlightSize', 'On Flight', 'black'),
83 | ]
84 | else:
85 | lines = [
86 | linedesc('pktRecv', 'Received', 'green'),
87 | linedesc('pktRcvLoss', 'Lost', 'orange'),
88 | linedesc('pktRcvRetrans', 'Retransmitted', 'blue'),
89 | linedesc('pktRcvBelated', 'Belated', 'grey'),
90 | linedesc('pktRcvDrop', 'Dropped', 'red'),
91 | ]
92 |
93 | return create_plot(
94 | 'Packets (' + side_name + ' Side)',
95 | 'Time (s)',
96 | 'Number of Packets',
97 | source,
98 | lines,
99 | models.NumeralTickFormatter(format='0,0')
100 | )
101 |
102 |
103 | def create_bytes_plot(source, is_sender):
104 | side_name = 'Sender' if is_sender else 'Receiver'
105 |
106 | if is_sender:
107 | lines = [
108 | linedesc('MBSent', 'Sent', 'green'),
109 | linedesc('MBSndDrop', 'Dropped', 'red')
110 | ]
111 | else:
112 | lines = [
113 | linedesc('MBRecv', 'Received', 'green'),
114 | linedesc('MBRcvDrop', 'Dropped', 'red'),
115 | ]
116 |
117 | return create_plot(
118 | 'Megabytes (' + side_name + ' Side)',
119 | 'Time (s)',
120 | 'MB',
121 | source,
122 | lines,
123 | models.NumeralTickFormatter(format='0,0.00')
124 | )
125 |
126 |
127 | def create_rate_plot(source, is_sender):
128 | side_name = 'Sending' if is_sender else 'Receiving'
129 |
130 | if is_sender:
131 | lines = [
132 | linedesc('mbpsSendRate', 'Sendrate', 'green'),
133 | # Please don't delete the following line.
134 | # It is used in some cases.
135 | #linedesc('mbpsMaxBW', 'Bandwidth Limit', 'black'),
136 | ]
137 | else:
138 | lines = [linedesc('mbpsRecvRate', '', 'green')]
139 |
140 | return create_plot(
141 | side_name + ' Rate',
142 | 'Time (s)',
143 | 'Rate (Mbps)',
144 | source,
145 | lines,
146 | models.NumeralTickFormatter(format='0,0.00')
147 | )
148 |
149 |
150 | def create_rtt_plot(source):
151 | lines = [linedesc('msRTT', '', 'blue')]
152 |
153 | return create_plot(
154 | 'Round-Trip Time',
155 | 'Time (s)',
156 | 'RTT (ms)',
157 | source,
158 | lines
159 | )
160 |
161 |
162 | def create_pkt_send_period_plot(source):
163 | lines = [linedesc('usPktSndPeriod', '', 'blue')]
164 |
165 | return create_plot(
166 | 'Packet Sending Period',
167 | 'Time (s)',
168 | 'Period (μs)',
169 | source,
170 | lines
171 | )
172 |
173 |
174 | def create_avail_buffer_plot(source, is_sender):
175 | side_name = 'Sending' if is_sender else 'Receiving'
176 |
177 | if is_sender:
178 | if not 'byteAvailSndBuf' in source.column_names:
179 | return None
180 | lines = [linedesc('MBAvailSndBuf', '', 'green')]
181 | else:
182 | if not 'byteAvailRcvBuf' in source.column_names:
183 | return None
184 | lines = [linedesc('MBAvailRcvBuf', '', 'green')]
185 |
186 | return create_plot(
187 | 'Available ' + side_name + ' Buffer Size',
188 | 'Time (s)',
189 | 'MB',
190 | source,
191 | lines,
192 | models.NumeralTickFormatter(format='0,0.00')
193 | )
194 |
195 |
196 | def create_window_size_plot(source):
197 | lines = [
198 | linedesc('pktFlowWindow', 'Flow Window', 'green'),
199 | linedesc('pktCongestionWindow', 'Congestion Window', 'red'),
200 | ]
201 |
202 | return create_plot(
203 | 'Window Size',
204 | 'Time (s)',
205 | 'Number of Packets',
206 | source,
207 | lines,
208 | models.NumeralTickFormatter(format='0,0')
209 | )
210 |
211 | def create_buf_timespan_plot(source, is_sender):
212 | if is_sender and 'msSndBuf' in source.column_names:
213 | lines = [linedesc('msSndBuf', '', 'blue')]
214 |
215 | return create_plot(
216 | 'Sender Buffer Fullness',
217 | 'Time (s)',
218 | 'Timespan (ms)',
219 | source,
220 | lines
221 | )
222 |
223 | if (not is_sender) and 'msRcvBuf' in source.column_names:
224 | lines = [linedesc('msRcvBuf', '', 'blue')]
225 |
226 | return create_plot(
227 | 'Receiver Buffer Fullness',
228 | 'Time (s)',
229 | 'Timespan (ms)',
230 | source,
231 | lines
232 | )
233 |
234 | return None
235 |
236 | def create_latency_plot(source):
237 | if 'RCVLATENCYms' in source.column_names:
238 | lines = [linedesc('RCVLATENCYms', '', 'blue')]
239 |
240 | return create_plot(
241 | 'Latency',
242 | 'Time (s)',
243 | 'Latency (ms)',
244 | source,
245 | lines
246 | )
247 |
248 | return None
249 |
250 |
251 | def create_bandwidth_plot(source):
252 | lines = [linedesc('mbpsBandwidth', '', 'green')]
253 |
254 | return create_plot(
255 | 'Bandwith',
256 | 'Time (s)',
257 | 'Bandwith (Mbps)',
258 | source,
259 | lines,
260 | models.NumeralTickFormatter(format='0,0')
261 | )
262 |
263 |
264 | # TODO: Implement
265 | # def plot_from_dir(dir_path):
266 | # stats_dir = dir_path
267 | # for filename in os.listdir(stats_dir):
268 | # if filename.endswith('.csv'):
269 | # name, _ = filename.split('.')
270 | # stats_file = stats_dir + '/' + filename
271 | # output_file = stats_dir + '/' + name + '.html'
272 | # plot(stats_file, output_file)
273 |
274 |
275 | # TODO: Move FEC related calculations out of this script
276 | def calculate_fec_stats(stats_file):
277 | df = pd.read_csv(stats_file)
278 |
279 | # Calculate summary and average FEC overhead
280 | df1 = df.sum(axis=0)
281 | srt_packets = (
282 | df1['pktRecv'] - df1['pktRcvFilterExtra']
283 | ) # srt packets only without extra FEC packets
284 | sum_overhead = round(df1['pktRcvFilterExtra'] * 100 / srt_packets, 2)
285 |
286 | s = df['pktRcvFilterExtra'] * 100 / (df['pktRecv'] - df['pktRcvFilterExtra'])
287 | avg_overhead = s.sum() / s.size
288 | avg_overhead = round(avg_overhead, 4)
289 |
290 | # Reconstructed and Not reconstructed packets
291 | sum_reconstructed = round(df1['pktRcvFilterSupply'] * 100 / srt_packets, 2)
292 | sum_not_reconstructed = round(df1['pktRcvFilterLoss'] * 100 / srt_packets, 2)
293 |
294 | print(f'fec_overhead: {sum_overhead} %')
295 | # print(f'avg_overhead: {avg_overhead} %')
296 | print(f'fec_reconstructed: {sum_reconstructed} %')
297 | print(f'fec_not_reconstructed: {sum_not_reconstructed} %')
298 |
299 |
300 | def calculate_fec_stats_from_directory(dir_path):
301 | stats_dir = dir_path
302 | for filename in os.listdir(stats_dir):
303 | if filename.endswith('.csv'):
304 | name, _ = filename.split('.')
305 | stats_file = stats_dir + '/' + filename
306 | print(stats_file)
307 | calculate_fec_stats(stats_file)
308 | calculate_received_packets_stats(stats_file)
309 |
310 |
311 | def calculate_received_packets_stats(stats_file):
312 | df = pd.read_csv(stats_file)
313 | df1 = df.sum(axis=0)
314 | srt_packets = (
315 | df1['pktRecv'] - df1['pktRcvFilterExtra']
316 | ) # srt packets only without extra FEC packets
317 |
318 | lost = round(df1['pktRcvLoss'] * 100 / srt_packets, 2)
319 | retransmitted = round(df1['pktRcvRetrans'] * 100 / srt_packets, 2)
320 | dropped = round(df1['pktRcvDrop'] * 100 / srt_packets, 2)
321 | belated = round(df1['pktRcvBelated'] * 100 / srt_packets, 2)
322 |
323 | print(f'lost: {lost} %')
324 | print(f'retransmitted: {retransmitted} %')
325 | print(f'dropped: {dropped} %')
326 | print(f'belated: {belated} %')
327 |
328 |
329 | @click.command()
330 | @click.argument(
331 | 'stats_filepath',
332 | type=click.Path(exists=True)
333 | )
334 | @click.option(
335 | '--is-sender',
336 | is_flag=True,
337 | default=False,
338 | help= 'Should be set if sender statistics is provided. Otherwise, '
339 | 'it is assumed that receiver statistics is provided.',
340 | show_default=True
341 | )
342 | @click.option(
343 | '--is-fec',
344 | is_flag=True,
345 | default=False,
346 | help='Should be set if packet filter (FEC) stats is enabled.',
347 | show_default=True
348 | )
349 | @click.option(
350 | '--export-png',
351 | is_flag=True,
352 | default=False,
353 | help='Export plots to .png files.',
354 | show_default=True
355 | )
356 | def plot_graph(stats_filepath, is_sender, is_fec, export_png):
357 | """
358 | This script processes .csv file with SRT core statistics produced by
359 | test application and visualizes the data. Depending on whether
360 | statistics is collected on sender or receiver side, the plots may vary.
361 | """
362 | filepath = pathlib.Path(stats_filepath)
363 | filename = filepath.name
364 |
365 | if not filename.endswith('.csv'):
366 | raise IsNotCSVFile(f'{filepath} does not correspond to a .csv file')
367 |
368 | name, _ = filename.rsplit('.', 1)
369 | name_parts = name.split('-')
370 | html_filename = name + '.html'
371 | html_filepath = filepath.parent / html_filename
372 |
373 | # Additional input filename checks
374 | if 'snd' in name_parts and not is_sender:
375 | print(
376 | 'Stats filename corresponds to a sender statistics, however, '
377 | 'is_sender flag is not set. Further stats processing will be '
378 | 'done as in case of sender statistics.'
379 | )
380 | is_sender = True
381 |
382 | if 'rcv' in name_parts and is_sender:
383 | print(
384 | 'Stats filename corresponds to a receiver statistics, however, '
385 | 'is_sender flag is set in True. Further stats processing will be '
386 | 'done as in case of receiver statistics.'
387 | )
388 | is_sender = False
389 |
390 | # Prepare data
391 | df = pd.read_csv(filepath)
392 |
393 | if 'Timepoint' in df.columns:
394 | df['Timepoint'] = pd.to_datetime(df['Timepoint'])
395 | df['TTime'] = df['Timepoint'] - df['Timepoint'].iloc[0]
396 | df['sTTime'] = df['TTime'].dt.total_seconds()
397 | else:
398 | df['sTTime'] = df['Time'] / 1000
399 |
400 | DIVISOR = 1000000
401 | df['MBRecv'] = df['byteRecv'] / DIVISOR
402 | df['MBRcvDrop'] = df['byteRcvDrop'] / DIVISOR
403 | df['MBSent'] = df['byteSent'] / DIVISOR
404 | df['MBSndDrop'] = df['byteSndDrop'] / DIVISOR
405 | if 'byteAvailRcvBuf' in df.columns:
406 | df['MBAvailRcvBuf'] = df['byteAvailRcvBuf'] / DIVISOR
407 | if 'byteAvailSndBuf' in df.columns:
408 | df['MBAvailSndBuf'] = df['byteAvailSndBuf'] / DIVISOR
409 |
410 | source = models.ColumnDataSource(df)
411 |
412 | # Output to static .html file
413 | plotting.output_file(html_filepath, title="SRT Stats Visualization")
414 |
415 | # A dict for storing plots
416 | plots = {}
417 |
418 | # Create plots
419 | # RTT
420 | plots['rtt'] = create_rtt_plot(source)
421 | export_plot_png(export_png, plots['rtt'], name, 'rtt')
422 |
423 | # Packets Statistics (receiver or sender)
424 | plots['packets'] = create_packets_plot(source, is_sender)
425 | export_plot_png(export_png, plots['packets'], name, 'packets')
426 |
427 | # Bandwidth
428 | plots['bw'] = create_bandwidth_plot(source)
429 |
430 | # Sending / Receiving Rate
431 | plots['rate'] = create_rate_plot(source, is_sender)
432 | export_plot_png(export_png, plots['rate'], name, 'rate')
433 |
434 | # Sending / Receiving Bytes
435 | plots['bytes'] = create_bytes_plot(source, is_sender)
436 |
437 | # Window Size
438 | plots['window_size'] = create_window_size_plot(source)
439 |
440 | # Latency
441 | #plots['latency'] = create_latency_plot(source)
442 | plots['msbuf'] = create_buf_timespan_plot(source, is_sender)
443 |
444 | # Packet Sending Period
445 | plot_packet_period = None
446 | if is_sender:
447 | plot_packet_period = create_pkt_send_period_plot(source)
448 | # TODO: ? Why plot_packet_period is here?
449 | # Function create_pkt_send_period_plot does not return None
450 | if export_png and plot_packet_period:
451 | # The following two lines remove toolbar from PNG
452 | plot_packet_period.toolbar.logo = None
453 | plot_packet_period.toolbar_location = None
454 | bokeh.io.export_png(
455 | plot_packet_period, filename=f'{name}-pktsendperiod.png'
456 | )
457 | plots['packet_sending_period'] = plot_packet_period
458 |
459 | # Available Buffers
460 | plot_avail_1 = create_avail_buffer_plot(source, is_sender)
461 | if export_png and plot_avail_1:
462 | plot_avail_1.toolbar.logo = None
463 | plot_avail_1.toolbar_location = None
464 | bokeh.io.export_png(plot_avail_1, filename=f'{name}-availbuffer.png')
465 | if plot_avail_1:
466 | plots['available_buffer_snd'] = plot_avail_1
467 |
468 | plot_avail_2 = create_avail_buffer_plot(source, not is_sender)
469 | if plot_avail_2:
470 | plots['available_buffer_rcv'] = plot_avail_2
471 |
472 | # Receiver Statisitcs
473 | plot_fec = None
474 | if is_fec:
475 | plot_fec = create_figure(PLOT_WIDTH, PLOT_HEIGHT, TOOLS)
476 |
477 | plot_fec.title.text = 'FEC - Packets (Receiver Side)'
478 | plot_fec.xaxis.axis_label = 'Time (s)'
479 | plot_fec.yaxis.axis_label = 'Number of Packets'
480 | plot_fec.line(
481 | x='sTTime',
482 | y='pktRcvFilterExtra',
483 | color='blue',
484 | legend_label='Extra received',
485 | source=source,
486 | )
487 | plot_fec.line(
488 | x='sTTime',
489 | y='pktRcvFilterSupply',
490 | color='green',
491 | legend_label='Reconstructed',
492 | source=source,
493 | )
494 | plot_fec.line(
495 | x='sTTime',
496 | y='pktRcvFilterLoss',
497 | color='red',
498 | legend_label='Not reconstructed',
499 | source=source,
500 | )
501 |
502 | plots['fec'] = plot_fec
503 |
504 | # Syncronize x-ranges of figures
505 | last_key = list(plots)[-1]
506 | last_fig = plots[last_key]
507 |
508 | for fig in plots.values():
509 | if fig is None:
510 | continue
511 | fig.x_range = last_fig.x_range
512 |
513 | # Show the results
514 | grid = layouts.gridplot(
515 | [
516 | [plots['packets'], plots['window_size']],
517 | [plots['bytes'], plots['rtt']],
518 | [plots['rate'], plots['bw']],
519 | [plot_packet_period, plots['msbuf']],
520 | [plot_avail_1, plot_avail_2],
521 | [plot_fec],
522 | ]
523 | )
524 | plotting.show(grid)
525 |
526 |
527 | if __name__ == '__main__':
528 | plot_graph()
529 |
--------------------------------------------------------------------------------