1#!/usr/bin/env python
2# Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3#
4# Use of this source code is governed by a BSD-style license
5# that can be found in the LICENSE file in the root of the source
6# tree. An additional intellectual property rights grant can be found
7# in the file PATENTS.  All contributing project authors may
8# be found in the AUTHORS file in the root of the source tree.
9
10# This script is used to plot simulation dynamics. The expected format is
11# PLOT <plot_number> <var_name>:<ssrc>@<alg_name> <time> <value>
12# <var_name> may optionally be followed by #<axis_alignment> but it is
13# deprecated. <plot_number> is also deprecated.
14# Each combination <var_name>:<ssrc>@<alg_name> is stored in it's own time
15# series. The main function defines which time series should be displayed and
16# whether they should should be displayed in the same or separate windows.
17
18
19import matplotlib.pyplot as plt
20import numpy
21import re
22import sys
23
24# Change this to True to save the figure to a file. Look below for details.
25SAVE_FIGURE = False
26
27class ParsePlotLineException(Exception):
28  def __init__(self, reason, line):
29    super(ParsePlotLineException, self).__init__()
30    self.reason = reason
31    self.line = line
32
33
34def ParsePlotLine(line):
35  split_line = line.split()
36  if len(split_line) != 5:
37    raise ParsePlotLineException("Expected 5 arguments on line", line)
38  (plot, _, annotated_var, time, value) = split_line
39  if plot != "PLOT":
40    raise ParsePlotLineException("Line does not begin with \"PLOT\\t\"", line)
41  # The variable name can contain any non-whitespace character except "#:@"
42  match = re.match(r'([^\s#:@]+)(?:#\d)?:(\d+)@(\S+)', annotated_var)
43
44  if match == None:
45    raise ParsePlotLineException("Could not parse variable name, ssrc and \
46                                 algorithm name", annotated_var)
47  var_name = match.group(1)
48  ssrc = match.group(2)
49  alg_name = match.group(3).replace('_', ' ')
50
51  return (var_name, ssrc, alg_name, time, value)
52
53
54def GenerateLabel(var_name, ssrc, ssrc_count, alg_name):
55  label = var_name
56  if ssrc_count > 1 or ssrc != "0":
57    label = label + " flow " + ssrc
58  if alg_name != "-":
59    label = label + " " + alg_name
60  return label
61
62
63class Figure(object):
64  def __init__(self, name):
65    self.name = name
66    self.subplots = []
67
68  def AddSubplot(self, var_names, xlabel, ylabel):
69    self.subplots.append(Subplot(var_names, xlabel, ylabel))
70
71  def AddSample(self, var_name, ssrc, alg_name, time, value):
72    for s in self.subplots:
73      s.AddSample(var_name, ssrc, alg_name, time, value)
74
75  def PlotFigure(self, fig):
76    n = len(self.subplots)
77    for i in range(n):
78      axis = fig.add_subplot(n, 1, i+1)
79      self.subplots[i].PlotSubplot(axis)
80
81class Subplot(object):
82  def __init__(self, var_names, xlabel, ylabel):
83    self.xlabel = xlabel
84    self.ylabel = ylabel
85    self.var_names = var_names
86    self.samples = dict()
87
88  def AddSample(self, var_name, ssrc, alg_name, time, value):
89    if var_name not in self.var_names:
90      return
91
92    if alg_name not in self.samples.keys():
93      self.samples[alg_name] = {}
94    if ssrc not in self.samples[alg_name].keys():
95      self.samples[alg_name][ssrc] = {}
96    if var_name not in self.samples[alg_name][ssrc].keys():
97      self.samples[alg_name][ssrc][var_name] = []
98
99    self.samples[alg_name][ssrc][var_name].append((time, value))
100
101  def PlotSubplot(self, axis):
102    axis.set_xlabel(self.xlabel)
103    axis.set_ylabel(self.ylabel)
104
105    count = 0
106    for alg_name in self.samples.keys():
107      for ssrc in self.samples[alg_name].keys():
108        for var_name in self.samples[alg_name][ssrc].keys():
109          x = [sample[0] for sample in self.samples[alg_name][ssrc][var_name]]
110          y = [sample[1] for sample in self.samples[alg_name][ssrc][var_name]]
111          x = numpy.array(x)
112          y = numpy.array(y)
113          ssrc_count = len(self.samples[alg_name].keys())
114          l = GenerateLabel(var_name, ssrc, ssrc_count, alg_name)
115          if l == 'MaxThroughput_':
116            plt.plot(x, y, label=l, linewidth=4.0)
117          else:
118            plt.plot(x, y, label=l, linewidth=2.0)
119          count += 1
120
121    plt.grid(True)
122    if count > 1:
123      plt.legend(loc='best')
124
125
126def main():
127  receiver = Figure("PacketReceiver")
128  receiver.AddSubplot(['Throughput_kbps', 'MaxThroughput_', 'Capacity_kbps',
129                       'PerFlowCapacity_kbps', 'MetricRecorderThroughput_kbps'],
130                      "Time (s)", "Throughput (kbps)")
131  receiver.AddSubplot(['Delay_ms_', 'Delay_ms'], "Time (s)",
132                      "One-way delay (ms)")
133  receiver.AddSubplot(['Packet_Loss_'], "Time (s)", "Packet Loss Ratio")
134
135  kalman_state = Figure("KalmanState")
136  kalman_state.AddSubplot(['kc', 'km'], "Time (s)", "Kalman gain")
137  kalman_state.AddSubplot(['slope_1/bps'], "Time (s)", "Slope")
138  kalman_state.AddSubplot(['var_noise'], "Time (s)", "Var noise")
139
140  detector_state = Figure("DetectorState")
141  detector_state.AddSubplot(['T', 'threshold'], "Time (s)", "Offset")
142
143  trendline_state = Figure("TrendlineState")
144  trendline_state.AddSubplot(["accumulated_delay_ms", "smoothed_delay_ms"],
145                             "Time (s)", "Delay (ms)")
146  trendline_state.AddSubplot(["trendline_slope"], "Time (s)", "Slope")
147
148  target_bitrate = Figure("TargetBitrate")
149  target_bitrate.AddSubplot(['target_bitrate_bps', 'acknowledged_bitrate'],
150                            "Time (s)", "Bitrate (bps)")
151
152  min_rtt_state = Figure("MinRttState")
153  min_rtt_state.AddSubplot(['MinRtt'], "Time (s)", "Time (ms)")
154
155  # Select which figures to plot here.
156  figures = [receiver, detector_state, trendline_state, target_bitrate,
157    min_rtt_state]
158
159  # Add samples to the figures.
160  for line in sys.stdin:
161    if line.startswith("[ RUN      ]"):
162      test_name = re.search(r'\.(\w+)', line).group(1)
163    if line.startswith("PLOT"):
164      try:
165        (var_name, ssrc, alg_name, time, value) = ParsePlotLine(line)
166        for f in figures:
167          # The sample will be ignored bv the figures that don't need it.
168          f.AddSample(var_name, ssrc, alg_name, time, value)
169      except ParsePlotLineException as e:
170        print e.reason
171        print e.line
172
173  # Plot figures.
174  for f in figures:
175    fig = plt.figure(f.name)
176    f.PlotFigure(fig)
177    if SAVE_FIGURE:
178      fig.savefig(test_name + f.name + ".png")
179  plt.show()
180
181if __name__ == '__main__':
182  main()
183