OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. | 2 # Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. |
3 # | 3 # |
4 # Use of this source code is governed by a BSD-style license | 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 | 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 | 6 # tree. An additional intellectual property rights grant can be found |
7 # in the file PATENTS. All contributing project authors may | 7 # in the file PATENTS. All contributing project authors may |
8 # be found in the AUTHORS file in the root of the source tree. | 8 # be found in the AUTHORS file in the root of the source tree. |
9 | 9 |
10 # This script is used to plot simulation dynamics. | 10 # This script is used to plot simulation dynamics. The expected format is |
11 # Able to plot each flow separately. Other plot boxes can be added, | 11 # PLOT <plot_number> <var_name>:<ssrc>@<alg_name> <time> <value> |
12 # currently one for Throughput, one for Latency and one for Packet Loss. | 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. |
13 | 17 |
14 import matplotlib | 18 |
15 import matplotlib.pyplot as plt | 19 import matplotlib.pyplot as plt |
16 import numpy | 20 import numpy |
17 import re | 21 import re |
18 import sys | 22 import sys |
19 | 23 |
20 # Change this to True to save the figure to a file. Look below for details. | 24 # Change this to True to save the figure to a file. Look below for details. |
21 save_figure = False | 25 save_figure = False |
22 | 26 |
23 class Variable(object): | 27 class ParsePlotLineException(Exception): |
24 def __init__(self, variable): | 28 def __init__(self, reason, line): |
25 self._ID = variable[0] | 29 super(ParsePlotLineException, self).__init__() |
26 self._xlabel = variable[1] | 30 self.reason = reason |
27 self._ylabel = variable[2] | 31 self.line = line |
28 self._subplot = variable[3] | 32 |
29 self._y_max = variable[4] | 33 |
| 34 def parse_plot_line(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 |
| 54 def generate_label(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 |
| 63 class 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 ax = fig.add_subplot(n, 1, i+1) |
| 79 self.subplots[i].plotSubplot(ax) |
| 80 |
| 81 |
| 82 class Subplot(object): |
| 83 def __init__(self, var_names, xlabel, ylabel): |
| 84 self.xlabel = xlabel |
| 85 self.ylabel = ylabel |
| 86 self.var_names = var_names |
30 self.samples = dict() | 87 self.samples = dict() |
31 | 88 |
32 def getID(self): | 89 def addSample(self, var_name, ssrc, alg_name, time, value): |
33 return self._ID | 90 if var_name not in self.var_names: |
34 | 91 return |
35 def getXLabel(self): | |
36 return self._xlabel | |
37 | |
38 def getYLabel(self): | |
39 return self._ylabel | |
40 | |
41 def getSubplot(self): | |
42 return self._subplot | |
43 | |
44 def getYMax(self): | |
45 return self._y_max | |
46 | |
47 def getNumberOfFlows(self): | |
48 return len(self.samples) | |
49 | |
50 | |
51 def addSample(self, line): | |
52 groups = re.search(r'_(((\d)+((,(\d)+)*))_(\D+))#\d:(\d)@(\S+)', line) | |
53 | |
54 # Each variable will be plotted in a separated box. | |
55 var_name = groups.group(1) | |
56 alg_name = groups.group(9) | |
57 | |
58 alg_name = alg_name.replace('_', ' ') | |
59 | 92 |
60 if alg_name not in self.samples.keys(): | 93 if alg_name not in self.samples.keys(): |
61 self.samples[alg_name] = {} | 94 self.samples[alg_name] = {} |
| 95 if ssrc not in self.samples[alg_name].keys(): |
| 96 self.samples[alg_name][ssrc] = {} |
| 97 if var_name not in self.samples[alg_name][ssrc].keys(): |
| 98 self.samples[alg_name][ssrc][var_name] = [] |
62 | 99 |
63 if var_name not in self.samples[alg_name].keys(): | 100 self.samples[alg_name][ssrc][var_name].append((time, value)) |
64 self.samples[alg_name][var_name] = [] | |
65 | 101 |
66 sample = re.search(r'(\d+\.\d+)\t([-]?\d+\.\d+)', line) | 102 def plotSubplot(self, ax): |
| 103 ax.set_xlabel(self.xlabel) |
| 104 ax.set_ylabel(self.ylabel) |
67 | 105 |
68 s = (sample.group(1), sample.group(2)) | 106 count = 0 |
69 self.samples[alg_name][var_name].append(s) | 107 for alg_name in self.samples.keys(): |
| 108 for ssrc in self.samples[alg_name].keys(): |
| 109 for var_name in self.samples[alg_name][ssrc].keys(): |
| 110 x = [sample[0] for sample in self.samples[alg_name][ssrc][var_name]] |
| 111 y = [sample[1] for sample in self.samples[alg_name][ssrc][var_name]] |
| 112 x = numpy.array(x) |
| 113 y = numpy.array(y) |
70 | 114 |
71 def plotVar(v, ax, show_legend, show_x_label): | 115 ssrc_count = len(self.samples[alg_name].keys()) |
72 if show_x_label: | 116 l = generate_label(var_name, ssrc, ssrc_count, alg_name) |
73 ax.set_xlabel(v.getXLabel(), fontsize='large') | 117 plt.plot(x, y, label=l, linewidth=2.0) |
74 ax.set_ylabel(v.getYLabel(), fontsize='large') | 118 count += 1 |
75 | 119 |
76 for alg in v.samples.keys(): | 120 plt.grid(True) |
| 121 if count > 1: |
| 122 plt.legend(loc='best') |
77 | 123 |
78 for series in v.samples[alg].keys(): | |
79 | |
80 x = [sample[0] for sample in v.samples[alg][series]] | |
81 y = [sample[1] for sample in v.samples[alg][series]] | |
82 x = numpy.array(x) | |
83 y = numpy.array(y) | |
84 | |
85 line = plt.plot(x, y, label=alg, linewidth=4.0) | |
86 | |
87 colormap = {'Available0':'#AAAAAA', | |
88 'Available1':'#AAAAAA', | |
89 'GCC0':'#80D000', | |
90 'GCC1':'#008000', | |
91 'GCC2':'#00F000', | |
92 'GCC3':'#00B000', | |
93 'GCC4':'#70B020', | |
94 'NADA0':'#0000AA', | |
95 'NADA1':'#A0A0FF', | |
96 'NADA2':'#0000FF', | |
97 'NADA3':'#C0A0FF', | |
98 'NADA4':'#9060B0',} | |
99 | |
100 flow_id = re.search(r'(\d+(,\d+)*)', series) # One or multiple ids. | |
101 key = alg + flow_id.group(1) | |
102 | |
103 if key in colormap: | |
104 plt.setp(line, color=colormap[key]) | |
105 elif alg == 'TCP': | |
106 plt.setp(line, color='#AAAAAA') | |
107 else: | |
108 plt.setp(line, color='#654321') | |
109 | |
110 if alg.startswith('Available'): | |
111 plt.setp(line, linestyle='--') | |
112 plt.grid(True) | |
113 | |
114 # x1, x2, y1, y2 | |
115 _, x2, _, y2 = plt.axis() | |
116 if v.getYMax() >= 0: | |
117 y2 = v.getYMax() | |
118 plt.axis((0, x2, 0, y2)) | |
119 | |
120 if show_legend: | |
121 plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.40), | |
122 shadow=True, fontsize='large', ncol=len(v.samples)) | |
123 | 124 |
124 def main(): | 125 def main(): |
125 variables = [ | 126 receiver = Figure("PacketReceiver") |
126 ('Throughput_kbps', "Time (s)", "Throughput (kbps)", 1, 4000), | 127 receiver.addSubplot(['Throughput_kbps', 'MaxThroughput_', 'Capacity_kbps', |
127 ('Delay_ms', "Time (s)", "One-way Delay (ms)", 2, 500), | 128 'PerFlowCapacity_kbps', 'MetricRecorderThroughput_kbps'], |
128 ('Packet_Loss', "Time (s)", "Packet Loss Ratio", 3, 1.0), | 129 "Time (s)", "Throughput (kbps)") |
129 ] | 130 receiver.addSubplot(['Delay_ms_', 'Delay_ms'], "Time (s)", |
| 131 "One-way delay (ms)") |
| 132 receiver.addSubplot(['Packet_Loss_'], "Time (s)", "Packet Loss Ratio") |
130 | 133 |
131 var = [] | 134 kalman_state = Figure("KalmanState") |
| 135 kalman_state.addSubplot(['kc', 'km'], "Time (s)", "Kalman gain") |
| 136 kalman_state.addSubplot(['slope_1/bps'], "Time (s)", "Slope") |
| 137 kalman_state.addSubplot(['var_noise'], "Time (s)", "Var noise") |
132 | 138 |
133 # Create objects. | 139 detector_state = Figure("DetectorState") |
134 for variable in variables: | 140 detector_state.addSubplot(['offset_ms'], "Time (s)", "Offset") |
135 var.append(Variable(variable)) | 141 detector_state.addSubplot(['gamma_ms'], "Time (s)", "Gamma") |
136 | 142 |
137 # Add samples to the objects. | 143 # Select which figures to plot here. |
| 144 figures = [receiver, detector_state] |
| 145 |
| 146 # Add samples to the figures. |
138 for line in sys.stdin: | 147 for line in sys.stdin: |
139 if line.startswith("[ RUN ]"): | 148 if line.startswith("[ RUN ]"): |
140 test_name = re.search(r'\.(\w+)', line).group(1) | 149 test_name = re.search(r'\.(\w+)', line).group(1) |
141 if line.startswith("PLOT"): | 150 if line.startswith("PLOT"): |
142 for v in var: | 151 try: |
143 if v.getID() in line: | 152 (var_name, ssrc, alg_name, time, value) = parse_plot_line(line) |
144 v.addSample(line) | 153 for f in figures: |
| 154 # The sample will be ignored bv the figures that don't need it. |
| 155 f.addSample(var_name, ssrc, alg_name, time, value) |
| 156 except ParsePlotLineException as e: |
| 157 print e.reason |
| 158 print e.line |
145 | 159 |
146 matplotlib.rcParams.update({'font.size': 48/len(variables)}) | 160 # Plot figures. |
147 | 161 for f in figures: |
148 # Plot variables. | 162 fig = plt.figure(f.name) |
149 fig = plt.figure() | 163 f.plotFigure(fig) |
150 | 164 if save_figure: |
151 # Offest and threshold on the same plot. | 165 fig.savefig(test_name + f.name + ".png") |
152 n = var[-1].getSubplot() | |
153 i = 0 | |
154 for v in var: | |
155 ax = fig.add_subplot(n, 1, v.getSubplot()) | |
156 plotVar(v, ax, i == 0, i == n - 1) | |
157 i += 1 | |
158 | |
159 if save_figure: | |
160 fig.savefig(test_name + ".png") | |
161 plt.show() | 166 plt.show() |
162 | 167 |
163 if __name__ == '__main__': | 168 if __name__ == '__main__': |
164 main() | 169 main() |
OLD | NEW |