├── 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 |

4 | Code style: black 5 |

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 | packets_1 63 | 64 | The legends are interactive. You can click, e.g., on `Dropped` and `On Flight` to disable the respective plots: 65 | 66 | packets_2 67 | 68 | Another chart illustrates the sending rate in Mbps: 69 | 70 | sending_rate_mbps 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 | available_snd_buffer_size 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 | --------------------------------------------------------------------------------