Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 # Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. | |
| 2 # | |
| 3 # Use of this source code is governed by a BSD-style license | |
| 4 # that can be found in the LICENSE file in the root of the source | |
| 5 # tree. An additional intellectual property rights grant can be found | |
| 6 # in the file PATENTS. All contributing project authors may | |
| 7 # be found in the AUTHORS file in the root of the source tree. | |
| 8 | |
| 9 """Plots statistics from WebRTC integration test logs. | |
| 10 | |
| 11 Usage: $ python plot_webrtc_test_logs.py filename.txt | |
| 12 """ | |
| 13 | |
| 14 import numpy | |
| 15 import sys | |
| 16 import re | |
| 17 | |
| 18 import matplotlib.pyplot as plt | |
| 19 | |
| 20 # Log events. | |
| 21 EVENT_START = 'RUN ] TestWithBitrate/PlotVideoProcessorIntegrationTest.' | |
| 22 EVENT_END = 'OK ] TestWithBitrate/PlotVideoProcessorIntegrationTest.' | |
| 23 | |
| 24 # Metrics to plot, tuple: (name to parse in file, label to use when plotting). | |
| 25 BITRATE = ('Target Bitrate', 'bitrate (kbps)') | |
| 26 WIDTH = ('Width', 'width') | |
| 27 HEIGHT = ('Height', 'height') | |
| 28 FILENAME = ('Filename', 'clip') | |
| 29 CODEC_TYPE = ('Codec type', 'Codec') | |
| 30 ENCODER_IMPLEMENTATION_NAME = ('Encoder implementation name', 'enc name') | |
| 31 DECODER_IMPLEMENTATION_NAME = ('Decoder implementation name', 'dec name') | |
| 32 NUM_FRAMES = ('Total # of frames', 'num frames') | |
| 33 CORES = ('#CPU cores used', 'cores') | |
| 34 DENOISING = ('Denoising', 'denoising') | |
| 35 RESILIENCE = ('Resilience', 'resilience') | |
| 36 ERROR_CONCEALMENT = ('Error concealment', 'error concealment') | |
| 37 PSNR = ('PSNR avg', 'PSNR (dB)') | |
| 38 SSIM = ('SSIM avg', 'SSIM') | |
| 39 ENC_BITRATE = ('Encoding bitrate', 'encoded bitrate (kbps)') | |
| 40 FRAMERATE = ('Frame rate', 'fps') | |
| 41 NUM_DROPPED_FRAMES = ('Number of dropped frames', 'num dropped frames') | |
| 42 NUM_FRAMES_TO_TARGET = ('Number of frames to approach target rate', | |
| 43 'frames to reach target rate') | |
| 44 ENCODE_TIME = ('Encoding time', 'encode time (us)') | |
| 45 ENCODE_TIME_AVG = ('Encoding time', 'encode time (us) avg') | |
| 46 DECODE_TIME = ('Decoding time', 'decode time (us)') | |
| 47 DECODE_TIME_AVG = ('Decoding time', 'decode time (us) avg') | |
| 48 FRAME_SIZE = ('Frame sizes', 'frame size (bytes)') | |
| 49 FRAME_SIZE_AVG = ('Frame sizes', 'frame size (bytes) avg') | |
| 50 AVG_KEY_FRAME_SIZE = ('Average key frame size', 'avg key frame size (bytes)') | |
| 51 AVG_NON_KEY_FRAME_SIZE = ('Average non-key frame size', | |
| 52 'avg non-key frame size (bytes)') | |
| 53 | |
| 54 # Settings. | |
| 55 SETTINGS = [ | |
| 56 WIDTH, | |
| 57 HEIGHT, | |
| 58 FILENAME, | |
| 59 CODEC_TYPE, | |
| 60 NUM_FRAMES, | |
| 61 ENCODER_IMPLEMENTATION_NAME, | |
| 62 DECODER_IMPLEMENTATION_NAME, | |
| 63 ENCODE_TIME, | |
| 64 DECODE_TIME, | |
| 65 FRAME_SIZE, | |
| 66 ] | |
| 67 | |
| 68 # Settings, options for x-axis. | |
| 69 X_SETTINGS = [ | |
| 70 CORES, | |
| 71 FRAMERATE, | |
| 72 DENOISING, | |
| 73 RESILIENCE, | |
| 74 ERROR_CONCEALMENT, | |
| 75 BITRATE, # TODO(asapersson): Needs to be last. | |
| 76 ] | |
| 77 | |
| 78 # Results. | |
| 79 RESULTS = [ | |
| 80 PSNR, | |
| 81 SSIM, | |
| 82 ENC_BITRATE, | |
| 83 NUM_DROPPED_FRAMES, | |
| 84 NUM_FRAMES_TO_TARGET, | |
| 85 ENCODE_TIME_AVG, | |
| 86 DECODE_TIME_AVG, | |
| 87 AVG_KEY_FRAME_SIZE, | |
| 88 AVG_NON_KEY_FRAME_SIZE, | |
| 89 ] | |
| 90 | |
| 91 METRICS_TO_PARSE = [] | |
|
brandtr
2017/02/09 13:32:42
Could this be replaced by list concatenation?
MET
åsapersson
2017/02/09 16:39:37
Done.
| |
| 92 for k in SETTINGS: | |
| 93 METRICS_TO_PARSE.append(k) | |
| 94 for x_setting in X_SETTINGS: | |
| 95 METRICS_TO_PARSE.append(x_setting) | |
| 96 for result in RESULTS: | |
| 97 METRICS_TO_PARSE.append(result) | |
| 98 | |
| 99 Y_METRICS = [] | |
|
brandtr
2017/02/09 13:32:42
And here a list comprehension?
Y_METRICS = [ res[
åsapersson
2017/02/09 16:39:37
Done.
| |
| 100 for res in RESULTS: | |
| 101 Y_METRICS.append(res[1]) | |
| 102 | |
| 103 | |
| 104 # Parameters for plotting. | |
| 105 FIG_SIZE_SCALE_FACTOR_Y = 2.8 | |
| 106 FIG_SIZE_SCALE_FACTOR_X = 2 | |
|
brandtr
2017/02/09 13:32:42
Place FIG_SIZE_SCALE_FACTOR_X before the correspon
åsapersson
2017/02/09 16:39:37
Done.
| |
| 107 GRID_COLOR = [0.45, 0.45, 0.45] | |
| 108 | |
| 109 | |
| 110 def ToString(input_list): | |
|
brandtr
2017/02/09 13:32:42
Move ToString and ToStringWithoutMetric closer to
åsapersson
2017/02/09 16:39:37
Done.
| |
| 111 return ToStringWithoutMetric(input_list, ('', '')) | |
| 112 | |
| 113 | |
| 114 def ToStringWithoutMetric(input_list, metric): | |
| 115 i = 1 | |
| 116 output_str = "" | |
| 117 for m in input_list: | |
| 118 if m != metric: | |
| 119 output_str = output_str + ("%s. %s\n" % (i, m[1])) | |
| 120 i += 1 | |
| 121 return output_str | |
| 122 | |
| 123 | |
| 124 def ParseSetting(filename, setting): | |
| 125 """Parses setting from file. | |
| 126 | |
| 127 Args: | |
| 128 filename: The name of the file. | |
| 129 setting: Name of setting to parse (e.g. width). | |
| 130 | |
| 131 Returns: | |
| 132 A list holding parsed settings, e.g. ['width: 128.0', 'width: 160.0'] """ | |
| 133 | |
| 134 settings = [] | |
| 135 | |
| 136 f = open(filename) | |
| 137 while True: | |
| 138 line = f.readline() | |
| 139 if not line: | |
| 140 break | |
| 141 if re.search(r'%s' % EVENT_START, line): | |
| 142 # Parse event. | |
| 143 parsed = {} | |
| 144 while True: | |
| 145 line = f.readline() | |
| 146 if not line: | |
| 147 break | |
| 148 if re.search(r'%s' % EVENT_END, line): | |
| 149 # Add parsed setting to list. | |
| 150 if setting in parsed: | |
| 151 s = setting + ': ' + str(parsed[setting]) | |
| 152 if s not in settings: | |
| 153 settings.append(s) | |
| 154 break | |
| 155 | |
| 156 TryFindMetric(parsed, line, f) | |
| 157 | |
| 158 f.close() | |
| 159 return settings | |
| 160 | |
| 161 | |
| 162 def ParseMetrics(filename, setting1, setting2): | |
| 163 """Parses metrics from file. | |
| 164 | |
| 165 Args: | |
| 166 filename: The name of the file. | |
| 167 setting1: First setting for sorting metrics (e.g. width). | |
| 168 setting2: Second setting for sorting metrics (e.g. cores). | |
| 169 | |
| 170 Returns: | |
| 171 A dictionary holding parsed metrics. | |
| 172 | |
| 173 For example: | |
| 174 metrics[key1][key2][measurement] | |
| 175 | |
| 176 metrics = { | |
| 177 "width: 352": { | |
| 178 "cores: 1.0": { | |
| 179 "encode time (us)": [0.718005, 0.806925, 0.909726, 0.931835, 0.953642], | |
| 180 "PSNR (dB)": [25.546029, 29.465518, 34.723535, 36.428493, 38.686551], | |
| 181 "bitrate (kbps)": [50, 100, 300, 500, 1000] | |
| 182 }, | |
| 183 "cores: 2.0": { | |
| 184 "encode time (us)": [0.718005, 0.806925, 0.909726, 0.931835, 0.953642], | |
| 185 "PSNR (dB)": [25.546029, 29.465518, 34.723535, 36.428493, 38.686551], | |
| 186 "bitrate (kbps)": [50, 100, 300, 500, 1000] | |
| 187 }, | |
| 188 }, | |
| 189 "width: 176": { | |
| 190 "cores: 1.0": { | |
| 191 "encode time (us)": [0.857897, 0.91608, 0.959173, 0.971116, 0.980961], | |
| 192 "PSNR (dB)": [30.243646, 33.375592, 37.574387, 39.42184, 41.437897], | |
| 193 "bitrate (kbps)": [50, 100, 300, 500, 1000] | |
| 194 }, | |
| 195 } | |
| 196 } """ | |
| 197 | |
| 198 metrics = {} | |
| 199 | |
| 200 # Parse events. | |
| 201 f = open(filename) | |
| 202 while True: | |
| 203 line = f.readline() | |
| 204 if not line: | |
| 205 break | |
| 206 if re.search(r'%s' % EVENT_START, line): | |
| 207 # Parse event. | |
| 208 parsed = {} | |
| 209 while True: | |
| 210 line = f.readline() | |
| 211 if not line: | |
| 212 break | |
| 213 if re.search(r'%s' % EVENT_END, line): | |
| 214 # Add parsed values to metrics. | |
| 215 key1 = setting1 + ': ' + str(parsed[setting1]) | |
| 216 key2 = setting2 + ': ' + str(parsed[setting2]) | |
| 217 if key1 not in metrics: | |
| 218 metrics[key1] = {} | |
| 219 if key2 not in metrics[key1]: | |
| 220 metrics[key1][key2] = {} | |
| 221 | |
| 222 for label in parsed: | |
| 223 if label not in metrics[key1][key2]: | |
| 224 metrics[key1][key2][label] = [] | |
| 225 metrics[key1][key2][label].append(parsed[label]) | |
| 226 | |
| 227 break | |
| 228 | |
| 229 TryFindMetric(parsed, line, f) | |
| 230 | |
| 231 f.close() | |
| 232 return metrics | |
| 233 | |
| 234 | |
| 235 def TryFindMetric(parsed, line, f): | |
| 236 for metric in METRICS_TO_PARSE: | |
| 237 name = metric[0] | |
| 238 label = metric[1] | |
| 239 if re.search(r'%s' % name, line): | |
| 240 found, value = GetMetric(name, line) | |
| 241 if not found: | |
| 242 # TODO(asapersson): Change format. | |
| 243 # Try find min, max, average stats. | |
| 244 found, minimum = GetMetric("Min", f.readline()) | |
| 245 if not found: | |
| 246 return | |
| 247 found, maximum = GetMetric("Max", f.readline()) | |
| 248 if not found: | |
| 249 return | |
| 250 found, average = GetMetric("Average", f.readline()) | |
| 251 if not found: | |
| 252 return | |
| 253 | |
| 254 parsed[label + ' min'] = minimum | |
| 255 parsed[label + ' max'] = maximum | |
| 256 parsed[label + ' avg'] = average | |
| 257 | |
| 258 parsed[label] = value | |
| 259 return | |
| 260 | |
| 261 | |
| 262 def GetMetric(name, string): | |
| 263 # Float. | |
| 264 pattern = r'%s\s*[:=]\s*([+-]?\d+\.*\d*)' % name | |
|
brandtr
2017/02/09 13:32:42
I don't fully understand this regex. Maybe a short
åsapersson
2017/02/09 16:39:37
Done.
| |
| 265 m = re.search(r'%s' % pattern, string) | |
| 266 if m is not None: | |
| 267 return StringToFloat(m.group(1)) | |
| 268 | |
| 269 # Alphanumeric characters. | |
| 270 pattern = r'%s\s*[:=]\s*(\w+)' % name | |
|
brandtr
2017/02/09 13:32:42
Same thing here.
åsapersson
2017/02/09 16:39:37
Done.
| |
| 271 m = re.search(r'%s' % pattern, string) | |
| 272 if m is not None: | |
| 273 return True, m.group(1) | |
| 274 | |
| 275 return False, -1 | |
| 276 | |
| 277 | |
| 278 def StringToFloat(value): | |
| 279 try: | |
| 280 value = float(value) | |
| 281 except ValueError: | |
| 282 print "Not a float, skipped %s" % value | |
| 283 return False, -1 | |
| 284 | |
| 285 return True, value | |
| 286 | |
| 287 | |
| 288 def Plot(y_metric, x_metric, metrics): | |
| 289 """Plots y_metric vs x_metric per key in metrics. | |
| 290 | |
| 291 For example: | |
| 292 y_metric = 'PSNR (dB)' | |
| 293 x_metric = 'bitrate (kbps)' | |
| 294 metrics = { | |
| 295 "cores: 1.0": { | |
| 296 "PSNR (dB)": [25.546029, 29.465518, 34.723535, 36.428493, 38.686551], | |
| 297 "bitrate (kbps)": [50, 100, 300, 500, 1000] | |
| 298 }, | |
| 299 "cores: 2.0": { | |
| 300 "PSNR (dB)": [25.546029, 29.465518, 34.723535, 36.428493, 38.686551], | |
| 301 "bitrate (kbps)": [50, 100, 300, 500, 1000] | |
| 302 }, | |
| 303 } | |
| 304 """ | |
| 305 for key in metrics: | |
| 306 data = metrics[key] | |
| 307 if y_metric not in data: | |
| 308 print "Failed to find metric: %s" % y_metric | |
| 309 continue | |
| 310 | |
| 311 y = numpy.array(data[y_metric]) | |
| 312 x = numpy.array(data[x_metric]) | |
| 313 if len(y) != len(x): | |
| 314 print "Length mismatch for %s, %s" % (y, x) | |
| 315 continue | |
| 316 | |
| 317 label = y_metric + ' - ' + str(key) | |
| 318 | |
| 319 plt.plot(x, y, label=label, linewidth=1.5, marker='o', markersize=5, | |
| 320 markeredgewidth=0.0) | |
| 321 | |
| 322 | |
| 323 def PlotFigure(settings, y_metrics, x_metric, metrics, title): | |
| 324 """Plots metrics in y_metrics list. One figure is plotted and each entry | |
| 325 in the list is plotted in a subplot (and sorted per settings). | |
| 326 | |
| 327 For example: | |
| 328 settings = ['width: 128.0', 'width: 160.0']. Sort subplot per setting. | |
| 329 y_metrics = ['PSNR (dB)', 'PSNR (dB)']. Metric to plot per subplot. | |
| 330 x_metric = 'bitrate (kbps)' | |
| 331 | |
| 332 """ | |
| 333 | |
| 334 plt.figure() | |
| 335 plt.suptitle(title, fontsize='small', fontweight='bold') | |
| 336 rows = len(settings) | |
| 337 cols = 1 | |
| 338 pos = 1 | |
| 339 while pos <= rows: | |
| 340 plt.rc('grid', color=GRID_COLOR) | |
| 341 ax = plt.subplot(rows, cols, pos) | |
| 342 plt.grid() | |
| 343 plt.setp(ax.get_xticklabels(), visible=(pos == rows), fontsize='small') | |
| 344 plt.setp(ax.get_yticklabels(), fontsize='small') | |
| 345 setting = settings[pos - 1] | |
| 346 Plot(y_metrics[pos - 1], x_metric, metrics[setting]) | |
| 347 plt.title(setting, fontsize='x-small') | |
| 348 plt.legend(fontsize='xx-small') | |
| 349 pos += 1 | |
| 350 | |
| 351 plt.xlabel(x_metric, fontsize='small') | |
| 352 plt.subplots_adjust(left=0.04, right=0.98, bottom=0.04, top=0.96, hspace=0.1) | |
| 353 | |
| 354 | |
| 355 def GetTitle(filename): | |
| 356 title = '' | |
| 357 codec_types = ParseSetting(filename, CODEC_TYPE[1]) | |
| 358 for i in range(0, len(codec_types)): | |
| 359 title += codec_types[i] + ', ' | |
| 360 | |
| 361 framerate = ParseSetting(filename, FRAMERATE[1]) | |
| 362 for i in range(0, len(framerate)): | |
| 363 title += framerate[i].split('.')[0] + ', ' | |
| 364 | |
| 365 enc_names = ParseSetting(filename, ENCODER_IMPLEMENTATION_NAME[1]) | |
| 366 for i in range(0, len(enc_names)): | |
| 367 title += enc_names[i] + ', ' | |
| 368 | |
| 369 dec_names = ParseSetting(filename, DECODER_IMPLEMENTATION_NAME[1]) | |
| 370 for i in range(0, len(dec_names)): | |
| 371 title += dec_names[i] + ', ' | |
| 372 | |
| 373 filenames = ParseSetting(filename, FILENAME[1]) | |
| 374 title += filenames[0].split('_')[0] | |
| 375 | |
| 376 num_frames = ParseSetting(filename, NUM_FRAMES[1]) | |
| 377 for i in range(0, len(num_frames)): | |
| 378 title += ' (' + num_frames[i].split('.')[0] + ')' | |
| 379 | |
| 380 return title | |
| 381 | |
| 382 | |
| 383 def GetIdx(text_list): | |
| 384 return int(raw_input(text_list)) - 1 | |
| 385 | |
| 386 | |
| 387 def main(): | |
| 388 filename = sys.argv[1] | |
| 389 | |
| 390 # Setup. | |
| 391 idx_metric = GetIdx("Choose metric:\n0. All\n%s" % ToString(RESULTS)) | |
| 392 if idx_metric == -1: | |
| 393 # Plot all metrics. One subplot for each metric. | |
| 394 # Per subplot: metric vs bitrate (per resolution). | |
| 395 cores = ParseSetting(filename, CORES[1]) | |
| 396 setting1 = CORES[1] | |
| 397 setting2 = WIDTH[1] | |
| 398 sub_keys = [cores[0]] * len(Y_METRICS) | |
| 399 y_metrics = Y_METRICS | |
| 400 x_metric = BITRATE[1] | |
| 401 else: | |
| 402 resolutions = ParseSetting(filename, WIDTH[1]) | |
| 403 idx = GetIdx("Select metric for x-axis:\n%s" % ToString(X_SETTINGS)) | |
| 404 if X_SETTINGS[idx] == BITRATE: | |
| 405 idx = GetIdx("Plot per:\n%s" % ToStringWithoutMetric(X_SETTINGS, BITRATE)) | |
| 406 idx_setting = METRICS_TO_PARSE.index(X_SETTINGS[idx]) | |
| 407 # Plot one metric. One subplot for each resolution. | |
| 408 # Per subplot: metric vs bitrate (per setting). | |
| 409 setting1 = WIDTH[1] | |
| 410 setting2 = METRICS_TO_PARSE[idx_setting][1] | |
| 411 sub_keys = resolutions | |
| 412 y_metrics = [RESULTS[idx_metric][1]] * len(sub_keys) | |
| 413 x_metric = BITRATE[1] | |
| 414 else: | |
| 415 # Plot one metric. One subplot for each resolution. | |
| 416 # Per subplot: metric vs setting (per bitrate). | |
| 417 setting1 = WIDTH[1] | |
| 418 setting2 = BITRATE[1] | |
| 419 sub_keys = resolutions | |
| 420 y_metrics = [RESULTS[idx_metric][1]] * len(sub_keys) | |
| 421 x_metric = X_SETTINGS[idx][1] | |
| 422 | |
| 423 metrics = ParseMetrics(filename, setting1, setting2) | |
| 424 | |
| 425 # Stretch fig size. | |
| 426 figsize = plt.rcParams["figure.figsize"] | |
| 427 figsize[0] *= FIG_SIZE_SCALE_FACTOR_X | |
| 428 figsize[1] *= FIG_SIZE_SCALE_FACTOR_Y | |
| 429 plt.rcParams["figure.figsize"] = figsize | |
| 430 | |
| 431 PlotFigure(sub_keys, y_metrics, x_metric, metrics, GetTitle(filename)) | |
| 432 | |
| 433 plt.show() | |
| 434 | |
| 435 | |
| 436 if __name__ == '__main__': | |
| 437 main() | |
| OLD | NEW |