1*d9f75844SAndroid Build Coastguard Worker#!/usr/bin/env python 2*d9f75844SAndroid Build Coastguard Worker# Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. 3*d9f75844SAndroid Build Coastguard Worker# 4*d9f75844SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license 5*d9f75844SAndroid Build Coastguard Worker# that can be found in the LICENSE file in the root of the source 6*d9f75844SAndroid Build Coastguard Worker# tree. An additional intellectual property rights grant can be found 7*d9f75844SAndroid Build Coastguard Worker# in the file PATENTS. All contributing project authors may 8*d9f75844SAndroid Build Coastguard Worker# be found in the AUTHORS file in the root of the source tree. 9*d9f75844SAndroid Build Coastguard Worker"""Generate graphs for data generated by loopback tests. 10*d9f75844SAndroid Build Coastguard Worker 11*d9f75844SAndroid Build Coastguard WorkerUsage examples: 12*d9f75844SAndroid Build Coastguard Worker Show end to end time for a single full stack test. 13*d9f75844SAndroid Build Coastguard Worker ./full_stack_tests_plot.py -df end_to_end -o 600 --frames 1000 vp9_data.txt 14*d9f75844SAndroid Build Coastguard Worker 15*d9f75844SAndroid Build Coastguard Worker Show simultaneously PSNR and encoded frame size for two different runs of 16*d9f75844SAndroid Build Coastguard Worker full stack test. Averaged over a cycle of 200 frames. Used e.g. for 17*d9f75844SAndroid Build Coastguard Worker screenshare slide test. 18*d9f75844SAndroid Build Coastguard Worker ./full_stack_tests_plot.py -c 200 -df psnr -drf encoded_frame_size \\ 19*d9f75844SAndroid Build Coastguard Worker before.txt after.txt 20*d9f75844SAndroid Build Coastguard Worker 21*d9f75844SAndroid Build Coastguard Worker Similar to the previous test, but multiple graphs. 22*d9f75844SAndroid Build Coastguard Worker ./full_stack_tests_plot.py -c 200 -df psnr vp8.txt vp9.txt --next \\ 23*d9f75844SAndroid Build Coastguard Worker -c 200 -df sender_time vp8.txt vp9.txt --next \\ 24*d9f75844SAndroid Build Coastguard Worker -c 200 -df end_to_end vp8.txt vp9.txt 25*d9f75844SAndroid Build Coastguard Worker""" 26*d9f75844SAndroid Build Coastguard Worker 27*d9f75844SAndroid Build Coastguard Workerimport argparse 28*d9f75844SAndroid Build Coastguard Workerfrom collections import defaultdict 29*d9f75844SAndroid Build Coastguard Workerimport itertools 30*d9f75844SAndroid Build Coastguard Workerimport sys 31*d9f75844SAndroid Build Coastguard Workerimport matplotlib.pyplot as plt 32*d9f75844SAndroid Build Coastguard Workerimport numpy 33*d9f75844SAndroid Build Coastguard Worker 34*d9f75844SAndroid Build Coastguard Worker# Fields 35*d9f75844SAndroid Build Coastguard WorkerDROPPED = 0 36*d9f75844SAndroid Build Coastguard WorkerINPUT_TIME = 1 # ms (timestamp) 37*d9f75844SAndroid Build Coastguard WorkerSEND_TIME = 2 # ms (timestamp) 38*d9f75844SAndroid Build Coastguard WorkerRECV_TIME = 3 # ms (timestamp) 39*d9f75844SAndroid Build Coastguard WorkerRENDER_TIME = 4 # ms (timestamp) 40*d9f75844SAndroid Build Coastguard WorkerENCODED_FRAME_SIZE = 5 # bytes 41*d9f75844SAndroid Build Coastguard WorkerPSNR = 6 42*d9f75844SAndroid Build Coastguard WorkerSSIM = 7 43*d9f75844SAndroid Build Coastguard WorkerENCODE_TIME = 8 # ms (time interval) 44*d9f75844SAndroid Build Coastguard Worker 45*d9f75844SAndroid Build Coastguard WorkerTOTAL_RAW_FIELDS = 9 46*d9f75844SAndroid Build Coastguard Worker 47*d9f75844SAndroid Build Coastguard WorkerSENDER_TIME = TOTAL_RAW_FIELDS + 0 48*d9f75844SAndroid Build Coastguard WorkerRECEIVER_TIME = TOTAL_RAW_FIELDS + 1 49*d9f75844SAndroid Build Coastguard WorkerEND_TO_END = TOTAL_RAW_FIELDS + 2 50*d9f75844SAndroid Build Coastguard WorkerRENDERED_DELTA = TOTAL_RAW_FIELDS + 3 51*d9f75844SAndroid Build Coastguard Worker 52*d9f75844SAndroid Build Coastguard WorkerFIELD_MASK = 255 53*d9f75844SAndroid Build Coastguard Worker 54*d9f75844SAndroid Build Coastguard Worker# Options 55*d9f75844SAndroid Build Coastguard WorkerHIDE_DROPPED = 256 56*d9f75844SAndroid Build Coastguard WorkerRIGHT_Y_AXIS = 512 57*d9f75844SAndroid Build Coastguard Worker 58*d9f75844SAndroid Build Coastguard Worker# internal field id, field name, title 59*d9f75844SAndroid Build Coastguard Worker_FIELDS = [ 60*d9f75844SAndroid Build Coastguard Worker # Raw 61*d9f75844SAndroid Build Coastguard Worker (DROPPED, "dropped", "dropped"), 62*d9f75844SAndroid Build Coastguard Worker (INPUT_TIME, "input_time_ms", "input time"), 63*d9f75844SAndroid Build Coastguard Worker (SEND_TIME, "send_time_ms", "send time"), 64*d9f75844SAndroid Build Coastguard Worker (RECV_TIME, "recv_time_ms", "recv time"), 65*d9f75844SAndroid Build Coastguard Worker (ENCODED_FRAME_SIZE, "encoded_frame_size", "encoded frame size"), 66*d9f75844SAndroid Build Coastguard Worker (PSNR, "psnr", "PSNR"), 67*d9f75844SAndroid Build Coastguard Worker (SSIM, "ssim", "SSIM"), 68*d9f75844SAndroid Build Coastguard Worker (RENDER_TIME, "render_time_ms", "render time"), 69*d9f75844SAndroid Build Coastguard Worker (ENCODE_TIME, "encode_time_ms", "encode time"), 70*d9f75844SAndroid Build Coastguard Worker # Auto-generated 71*d9f75844SAndroid Build Coastguard Worker (SENDER_TIME, "sender_time", "sender time"), 72*d9f75844SAndroid Build Coastguard Worker (RECEIVER_TIME, "receiver_time", "receiver time"), 73*d9f75844SAndroid Build Coastguard Worker (END_TO_END, "end_to_end", "end to end"), 74*d9f75844SAndroid Build Coastguard Worker (RENDERED_DELTA, "rendered_delta", "rendered delta"), 75*d9f75844SAndroid Build Coastguard Worker] 76*d9f75844SAndroid Build Coastguard Worker 77*d9f75844SAndroid Build Coastguard WorkerNAME_TO_ID = {field[1]: field[0] for field in _FIELDS} 78*d9f75844SAndroid Build Coastguard WorkerID_TO_TITLE = {field[0]: field[2] for field in _FIELDS} 79*d9f75844SAndroid Build Coastguard Worker 80*d9f75844SAndroid Build Coastguard Worker 81*d9f75844SAndroid Build Coastguard Workerdef FieldArgToId(arg): 82*d9f75844SAndroid Build Coastguard Worker if arg == "none": 83*d9f75844SAndroid Build Coastguard Worker return None 84*d9f75844SAndroid Build Coastguard Worker if arg in NAME_TO_ID: 85*d9f75844SAndroid Build Coastguard Worker return NAME_TO_ID[arg] 86*d9f75844SAndroid Build Coastguard Worker if arg + "_ms" in NAME_TO_ID: 87*d9f75844SAndroid Build Coastguard Worker return NAME_TO_ID[arg + "_ms"] 88*d9f75844SAndroid Build Coastguard Worker raise Exception("Unrecognized field name \"{}\"".format(arg)) 89*d9f75844SAndroid Build Coastguard Worker 90*d9f75844SAndroid Build Coastguard Worker 91*d9f75844SAndroid Build Coastguard Workerclass PlotLine(object): 92*d9f75844SAndroid Build Coastguard Worker """Data for a single graph line.""" 93*d9f75844SAndroid Build Coastguard Worker 94*d9f75844SAndroid Build Coastguard Worker def __init__(self, label, values, flags): 95*d9f75844SAndroid Build Coastguard Worker self.label = label 96*d9f75844SAndroid Build Coastguard Worker self.values = values 97*d9f75844SAndroid Build Coastguard Worker self.flags = flags 98*d9f75844SAndroid Build Coastguard Worker 99*d9f75844SAndroid Build Coastguard Worker 100*d9f75844SAndroid Build Coastguard Workerclass Data(object): 101*d9f75844SAndroid Build Coastguard Worker """Object representing one full stack test.""" 102*d9f75844SAndroid Build Coastguard Worker 103*d9f75844SAndroid Build Coastguard Worker def __init__(self, filename): 104*d9f75844SAndroid Build Coastguard Worker self.title = "" 105*d9f75844SAndroid Build Coastguard Worker self.length = 0 106*d9f75844SAndroid Build Coastguard Worker self.samples = defaultdict(list) 107*d9f75844SAndroid Build Coastguard Worker 108*d9f75844SAndroid Build Coastguard Worker self._ReadSamples(filename) 109*d9f75844SAndroid Build Coastguard Worker 110*d9f75844SAndroid Build Coastguard Worker def _ReadSamples(self, filename): 111*d9f75844SAndroid Build Coastguard Worker """Reads graph data from the given file.""" 112*d9f75844SAndroid Build Coastguard Worker f = open(filename) 113*d9f75844SAndroid Build Coastguard Worker it = iter(f) 114*d9f75844SAndroid Build Coastguard Worker 115*d9f75844SAndroid Build Coastguard Worker self.title = it.next().strip() 116*d9f75844SAndroid Build Coastguard Worker self.length = int(it.next()) 117*d9f75844SAndroid Build Coastguard Worker field_names = [name.strip() for name in it.next().split()] 118*d9f75844SAndroid Build Coastguard Worker field_ids = [NAME_TO_ID[name] for name in field_names] 119*d9f75844SAndroid Build Coastguard Worker 120*d9f75844SAndroid Build Coastguard Worker for field_id in field_ids: 121*d9f75844SAndroid Build Coastguard Worker self.samples[field_id] = [0.0] * self.length 122*d9f75844SAndroid Build Coastguard Worker 123*d9f75844SAndroid Build Coastguard Worker for sample_id in xrange(self.length): 124*d9f75844SAndroid Build Coastguard Worker for col, value in enumerate(it.next().split()): 125*d9f75844SAndroid Build Coastguard Worker self.samples[field_ids[col]][sample_id] = float(value) 126*d9f75844SAndroid Build Coastguard Worker 127*d9f75844SAndroid Build Coastguard Worker self._SubtractFirstInputTime() 128*d9f75844SAndroid Build Coastguard Worker self._GenerateAdditionalData() 129*d9f75844SAndroid Build Coastguard Worker 130*d9f75844SAndroid Build Coastguard Worker f.close() 131*d9f75844SAndroid Build Coastguard Worker 132*d9f75844SAndroid Build Coastguard Worker def _SubtractFirstInputTime(self): 133*d9f75844SAndroid Build Coastguard Worker offset = self.samples[INPUT_TIME][0] 134*d9f75844SAndroid Build Coastguard Worker for field in [INPUT_TIME, SEND_TIME, RECV_TIME, RENDER_TIME]: 135*d9f75844SAndroid Build Coastguard Worker if field in self.samples: 136*d9f75844SAndroid Build Coastguard Worker self.samples[field] = [x - offset for x in self.samples[field]] 137*d9f75844SAndroid Build Coastguard Worker 138*d9f75844SAndroid Build Coastguard Worker def _GenerateAdditionalData(self): 139*d9f75844SAndroid Build Coastguard Worker """Calculates sender time, receiver time etc. from the raw data.""" 140*d9f75844SAndroid Build Coastguard Worker s = self.samples 141*d9f75844SAndroid Build Coastguard Worker last_render_time = 0 142*d9f75844SAndroid Build Coastguard Worker for field_id in [ 143*d9f75844SAndroid Build Coastguard Worker SENDER_TIME, RECEIVER_TIME, END_TO_END, RENDERED_DELTA 144*d9f75844SAndroid Build Coastguard Worker ]: 145*d9f75844SAndroid Build Coastguard Worker s[field_id] = [0] * self.length 146*d9f75844SAndroid Build Coastguard Worker 147*d9f75844SAndroid Build Coastguard Worker for k in range(self.length): 148*d9f75844SAndroid Build Coastguard Worker s[SENDER_TIME][k] = s[SEND_TIME][k] - s[INPUT_TIME][k] 149*d9f75844SAndroid Build Coastguard Worker 150*d9f75844SAndroid Build Coastguard Worker decoded_time = s[RENDER_TIME][k] 151*d9f75844SAndroid Build Coastguard Worker s[RECEIVER_TIME][k] = decoded_time - s[RECV_TIME][k] 152*d9f75844SAndroid Build Coastguard Worker s[END_TO_END][k] = decoded_time - s[INPUT_TIME][k] 153*d9f75844SAndroid Build Coastguard Worker if not s[DROPPED][k]: 154*d9f75844SAndroid Build Coastguard Worker if k > 0: 155*d9f75844SAndroid Build Coastguard Worker s[RENDERED_DELTA][k] = decoded_time - last_render_time 156*d9f75844SAndroid Build Coastguard Worker last_render_time = decoded_time 157*d9f75844SAndroid Build Coastguard Worker 158*d9f75844SAndroid Build Coastguard Worker def _Hide(self, values): 159*d9f75844SAndroid Build Coastguard Worker """ 160*d9f75844SAndroid Build Coastguard Worker Replaces values for dropped frames with None. 161*d9f75844SAndroid Build Coastguard Worker These values are then skipped by the Plot() method. 162*d9f75844SAndroid Build Coastguard Worker """ 163*d9f75844SAndroid Build Coastguard Worker 164*d9f75844SAndroid Build Coastguard Worker return [ 165*d9f75844SAndroid Build Coastguard Worker None if self.samples[DROPPED][k] else values[k] 166*d9f75844SAndroid Build Coastguard Worker for k in range(len(values)) 167*d9f75844SAndroid Build Coastguard Worker ] 168*d9f75844SAndroid Build Coastguard Worker 169*d9f75844SAndroid Build Coastguard Worker def AddSamples(self, config, target_lines_list): 170*d9f75844SAndroid Build Coastguard Worker """Creates graph lines from the current data set with given config.""" 171*d9f75844SAndroid Build Coastguard Worker for field in config.fields: 172*d9f75844SAndroid Build Coastguard Worker # field is None means the user wants just to skip the color. 173*d9f75844SAndroid Build Coastguard Worker if field is None: 174*d9f75844SAndroid Build Coastguard Worker target_lines_list.append(None) 175*d9f75844SAndroid Build Coastguard Worker continue 176*d9f75844SAndroid Build Coastguard Worker 177*d9f75844SAndroid Build Coastguard Worker field_id = field & FIELD_MASK 178*d9f75844SAndroid Build Coastguard Worker values = self.samples[field_id] 179*d9f75844SAndroid Build Coastguard Worker 180*d9f75844SAndroid Build Coastguard Worker if field & HIDE_DROPPED: 181*d9f75844SAndroid Build Coastguard Worker values = self._Hide(values) 182*d9f75844SAndroid Build Coastguard Worker 183*d9f75844SAndroid Build Coastguard Worker target_lines_list.append( 184*d9f75844SAndroid Build Coastguard Worker PlotLine(self.title + " " + ID_TO_TITLE[field_id], values, 185*d9f75844SAndroid Build Coastguard Worker field & ~FIELD_MASK)) 186*d9f75844SAndroid Build Coastguard Worker 187*d9f75844SAndroid Build Coastguard Worker 188*d9f75844SAndroid Build Coastguard Workerdef AverageOverCycle(values, length): 189*d9f75844SAndroid Build Coastguard Worker """ 190*d9f75844SAndroid Build Coastguard Worker Returns the list: 191*d9f75844SAndroid Build Coastguard Worker [ 192*d9f75844SAndroid Build Coastguard Worker avg(values[0], values[length], ...), 193*d9f75844SAndroid Build Coastguard Worker avg(values[1], values[length + 1], ...), 194*d9f75844SAndroid Build Coastguard Worker ... 195*d9f75844SAndroid Build Coastguard Worker avg(values[length - 1], values[2 * length - 1], ...), 196*d9f75844SAndroid Build Coastguard Worker ] 197*d9f75844SAndroid Build Coastguard Worker 198*d9f75844SAndroid Build Coastguard Worker Skips None values when calculating the average value. 199*d9f75844SAndroid Build Coastguard Worker """ 200*d9f75844SAndroid Build Coastguard Worker 201*d9f75844SAndroid Build Coastguard Worker total = [0.0] * length 202*d9f75844SAndroid Build Coastguard Worker count = [0] * length 203*d9f75844SAndroid Build Coastguard Worker for k, val in enumerate(values): 204*d9f75844SAndroid Build Coastguard Worker if val is not None: 205*d9f75844SAndroid Build Coastguard Worker total[k % length] += val 206*d9f75844SAndroid Build Coastguard Worker count[k % length] += 1 207*d9f75844SAndroid Build Coastguard Worker 208*d9f75844SAndroid Build Coastguard Worker result = [0.0] * length 209*d9f75844SAndroid Build Coastguard Worker for k in range(length): 210*d9f75844SAndroid Build Coastguard Worker result[k] = total[k] / count[k] if count[k] else None 211*d9f75844SAndroid Build Coastguard Worker return result 212*d9f75844SAndroid Build Coastguard Worker 213*d9f75844SAndroid Build Coastguard Worker 214*d9f75844SAndroid Build Coastguard Workerclass PlotConfig(object): 215*d9f75844SAndroid Build Coastguard Worker """Object representing a single graph.""" 216*d9f75844SAndroid Build Coastguard Worker 217*d9f75844SAndroid Build Coastguard Worker def __init__(self, 218*d9f75844SAndroid Build Coastguard Worker fields, 219*d9f75844SAndroid Build Coastguard Worker data_list, 220*d9f75844SAndroid Build Coastguard Worker cycle_length=None, 221*d9f75844SAndroid Build Coastguard Worker frames=None, 222*d9f75844SAndroid Build Coastguard Worker offset=0, 223*d9f75844SAndroid Build Coastguard Worker output_filename=None, 224*d9f75844SAndroid Build Coastguard Worker title="Graph"): 225*d9f75844SAndroid Build Coastguard Worker self.fields = fields 226*d9f75844SAndroid Build Coastguard Worker self.data_list = data_list 227*d9f75844SAndroid Build Coastguard Worker self.cycle_length = cycle_length 228*d9f75844SAndroid Build Coastguard Worker self.frames = frames 229*d9f75844SAndroid Build Coastguard Worker self.offset = offset 230*d9f75844SAndroid Build Coastguard Worker self.output_filename = output_filename 231*d9f75844SAndroid Build Coastguard Worker self.title = title 232*d9f75844SAndroid Build Coastguard Worker 233*d9f75844SAndroid Build Coastguard Worker def Plot(self, ax1): 234*d9f75844SAndroid Build Coastguard Worker lines = [] 235*d9f75844SAndroid Build Coastguard Worker for data in self.data_list: 236*d9f75844SAndroid Build Coastguard Worker if not data: 237*d9f75844SAndroid Build Coastguard Worker # Add None lines to skip the colors. 238*d9f75844SAndroid Build Coastguard Worker lines.extend([None] * len(self.fields)) 239*d9f75844SAndroid Build Coastguard Worker else: 240*d9f75844SAndroid Build Coastguard Worker data.AddSamples(self, lines) 241*d9f75844SAndroid Build Coastguard Worker 242*d9f75844SAndroid Build Coastguard Worker def _SliceValues(values): 243*d9f75844SAndroid Build Coastguard Worker if self.offset: 244*d9f75844SAndroid Build Coastguard Worker values = values[self.offset:] 245*d9f75844SAndroid Build Coastguard Worker if self.frames: 246*d9f75844SAndroid Build Coastguard Worker values = values[:self.frames] 247*d9f75844SAndroid Build Coastguard Worker return values 248*d9f75844SAndroid Build Coastguard Worker 249*d9f75844SAndroid Build Coastguard Worker length = None 250*d9f75844SAndroid Build Coastguard Worker for line in lines: 251*d9f75844SAndroid Build Coastguard Worker if line is None: 252*d9f75844SAndroid Build Coastguard Worker continue 253*d9f75844SAndroid Build Coastguard Worker 254*d9f75844SAndroid Build Coastguard Worker line.values = _SliceValues(line.values) 255*d9f75844SAndroid Build Coastguard Worker if self.cycle_length: 256*d9f75844SAndroid Build Coastguard Worker line.values = AverageOverCycle(line.values, self.cycle_length) 257*d9f75844SAndroid Build Coastguard Worker 258*d9f75844SAndroid Build Coastguard Worker if length is None: 259*d9f75844SAndroid Build Coastguard Worker length = len(line.values) 260*d9f75844SAndroid Build Coastguard Worker elif length != len(line.values): 261*d9f75844SAndroid Build Coastguard Worker raise Exception("All arrays should have the same length!") 262*d9f75844SAndroid Build Coastguard Worker 263*d9f75844SAndroid Build Coastguard Worker ax1.set_xlabel("Frame", fontsize="large") 264*d9f75844SAndroid Build Coastguard Worker if any(line.flags & RIGHT_Y_AXIS for line in lines if line): 265*d9f75844SAndroid Build Coastguard Worker ax2 = ax1.twinx() 266*d9f75844SAndroid Build Coastguard Worker ax2.set_xlabel("Frame", fontsize="large") 267*d9f75844SAndroid Build Coastguard Worker else: 268*d9f75844SAndroid Build Coastguard Worker ax2 = None 269*d9f75844SAndroid Build Coastguard Worker 270*d9f75844SAndroid Build Coastguard Worker # Have to implement color_cycle manually, due to two scales in a graph. 271*d9f75844SAndroid Build Coastguard Worker color_cycle = ["b", "r", "g", "c", "m", "y", "k"] 272*d9f75844SAndroid Build Coastguard Worker color_iter = itertools.cycle(color_cycle) 273*d9f75844SAndroid Build Coastguard Worker 274*d9f75844SAndroid Build Coastguard Worker for line in lines: 275*d9f75844SAndroid Build Coastguard Worker if not line: 276*d9f75844SAndroid Build Coastguard Worker color_iter.next() 277*d9f75844SAndroid Build Coastguard Worker continue 278*d9f75844SAndroid Build Coastguard Worker 279*d9f75844SAndroid Build Coastguard Worker if self.cycle_length: 280*d9f75844SAndroid Build Coastguard Worker x = numpy.array(range(self.cycle_length)) 281*d9f75844SAndroid Build Coastguard Worker else: 282*d9f75844SAndroid Build Coastguard Worker x = numpy.array( 283*d9f75844SAndroid Build Coastguard Worker range(self.offset, self.offset + len(line.values))) 284*d9f75844SAndroid Build Coastguard Worker y = numpy.array(line.values) 285*d9f75844SAndroid Build Coastguard Worker ax = ax2 if line.flags & RIGHT_Y_AXIS else ax1 286*d9f75844SAndroid Build Coastguard Worker ax.Plot(x, 287*d9f75844SAndroid Build Coastguard Worker y, 288*d9f75844SAndroid Build Coastguard Worker "o-", 289*d9f75844SAndroid Build Coastguard Worker label=line.label, 290*d9f75844SAndroid Build Coastguard Worker markersize=3.0, 291*d9f75844SAndroid Build Coastguard Worker linewidth=1.0, 292*d9f75844SAndroid Build Coastguard Worker color=color_iter.next()) 293*d9f75844SAndroid Build Coastguard Worker 294*d9f75844SAndroid Build Coastguard Worker ax1.grid(True) 295*d9f75844SAndroid Build Coastguard Worker if ax2: 296*d9f75844SAndroid Build Coastguard Worker ax1.legend(loc="upper left", shadow=True, fontsize="large") 297*d9f75844SAndroid Build Coastguard Worker ax2.legend(loc="upper right", shadow=True, fontsize="large") 298*d9f75844SAndroid Build Coastguard Worker else: 299*d9f75844SAndroid Build Coastguard Worker ax1.legend(loc="best", shadow=True, fontsize="large") 300*d9f75844SAndroid Build Coastguard Worker 301*d9f75844SAndroid Build Coastguard Worker 302*d9f75844SAndroid Build Coastguard Workerdef LoadFiles(filenames): 303*d9f75844SAndroid Build Coastguard Worker result = [] 304*d9f75844SAndroid Build Coastguard Worker for filename in filenames: 305*d9f75844SAndroid Build Coastguard Worker if filename in LoadFiles.cache: 306*d9f75844SAndroid Build Coastguard Worker result.append(LoadFiles.cache[filename]) 307*d9f75844SAndroid Build Coastguard Worker else: 308*d9f75844SAndroid Build Coastguard Worker data = Data(filename) 309*d9f75844SAndroid Build Coastguard Worker LoadFiles.cache[filename] = data 310*d9f75844SAndroid Build Coastguard Worker result.append(data) 311*d9f75844SAndroid Build Coastguard Worker return result 312*d9f75844SAndroid Build Coastguard Worker 313*d9f75844SAndroid Build Coastguard Worker 314*d9f75844SAndroid Build Coastguard WorkerLoadFiles.cache = {} 315*d9f75844SAndroid Build Coastguard Worker 316*d9f75844SAndroid Build Coastguard Worker 317*d9f75844SAndroid Build Coastguard Workerdef GetParser(): 318*d9f75844SAndroid Build Coastguard Worker class CustomAction(argparse.Action): 319*d9f75844SAndroid Build Coastguard Worker def __call__(self, parser, namespace, values, option_string=None): 320*d9f75844SAndroid Build Coastguard Worker if "ordered_args" not in namespace: 321*d9f75844SAndroid Build Coastguard Worker namespace.ordered_args = [] 322*d9f75844SAndroid Build Coastguard Worker namespace.ordered_args.append((self.dest, values)) 323*d9f75844SAndroid Build Coastguard Worker 324*d9f75844SAndroid Build Coastguard Worker parser = argparse.ArgumentParser( 325*d9f75844SAndroid Build Coastguard Worker description=__doc__, 326*d9f75844SAndroid Build Coastguard Worker formatter_class=argparse.RawDescriptionHelpFormatter) 327*d9f75844SAndroid Build Coastguard Worker 328*d9f75844SAndroid Build Coastguard Worker parser.add_argument("-c", 329*d9f75844SAndroid Build Coastguard Worker "--cycle_length", 330*d9f75844SAndroid Build Coastguard Worker nargs=1, 331*d9f75844SAndroid Build Coastguard Worker action=CustomAction, 332*d9f75844SAndroid Build Coastguard Worker type=int, 333*d9f75844SAndroid Build Coastguard Worker help="Cycle length over which to average the values.") 334*d9f75844SAndroid Build Coastguard Worker parser.add_argument( 335*d9f75844SAndroid Build Coastguard Worker "-f", 336*d9f75844SAndroid Build Coastguard Worker "--field", 337*d9f75844SAndroid Build Coastguard Worker nargs=1, 338*d9f75844SAndroid Build Coastguard Worker action=CustomAction, 339*d9f75844SAndroid Build Coastguard Worker help="Name of the field to show. Use 'none' to skip a color.") 340*d9f75844SAndroid Build Coastguard Worker parser.add_argument("-r", 341*d9f75844SAndroid Build Coastguard Worker "--right", 342*d9f75844SAndroid Build Coastguard Worker nargs=0, 343*d9f75844SAndroid Build Coastguard Worker action=CustomAction, 344*d9f75844SAndroid Build Coastguard Worker help="Use right Y axis for given field.") 345*d9f75844SAndroid Build Coastguard Worker parser.add_argument("-d", 346*d9f75844SAndroid Build Coastguard Worker "--drop", 347*d9f75844SAndroid Build Coastguard Worker nargs=0, 348*d9f75844SAndroid Build Coastguard Worker action=CustomAction, 349*d9f75844SAndroid Build Coastguard Worker help="Hide values for dropped frames.") 350*d9f75844SAndroid Build Coastguard Worker parser.add_argument("-o", 351*d9f75844SAndroid Build Coastguard Worker "--offset", 352*d9f75844SAndroid Build Coastguard Worker nargs=1, 353*d9f75844SAndroid Build Coastguard Worker action=CustomAction, 354*d9f75844SAndroid Build Coastguard Worker type=int, 355*d9f75844SAndroid Build Coastguard Worker help="Frame offset.") 356*d9f75844SAndroid Build Coastguard Worker parser.add_argument("-n", 357*d9f75844SAndroid Build Coastguard Worker "--next", 358*d9f75844SAndroid Build Coastguard Worker nargs=0, 359*d9f75844SAndroid Build Coastguard Worker action=CustomAction, 360*d9f75844SAndroid Build Coastguard Worker help="Separator for multiple graphs.") 361*d9f75844SAndroid Build Coastguard Worker parser.add_argument( 362*d9f75844SAndroid Build Coastguard Worker "--frames", 363*d9f75844SAndroid Build Coastguard Worker nargs=1, 364*d9f75844SAndroid Build Coastguard Worker action=CustomAction, 365*d9f75844SAndroid Build Coastguard Worker type=int, 366*d9f75844SAndroid Build Coastguard Worker help="Frame count to show or take into account while averaging.") 367*d9f75844SAndroid Build Coastguard Worker parser.add_argument("-t", 368*d9f75844SAndroid Build Coastguard Worker "--title", 369*d9f75844SAndroid Build Coastguard Worker nargs=1, 370*d9f75844SAndroid Build Coastguard Worker action=CustomAction, 371*d9f75844SAndroid Build Coastguard Worker help="Title of the graph.") 372*d9f75844SAndroid Build Coastguard Worker parser.add_argument("-O", 373*d9f75844SAndroid Build Coastguard Worker "--output_filename", 374*d9f75844SAndroid Build Coastguard Worker nargs=1, 375*d9f75844SAndroid Build Coastguard Worker action=CustomAction, 376*d9f75844SAndroid Build Coastguard Worker help="Use to save the graph into a file. " 377*d9f75844SAndroid Build Coastguard Worker "Otherwise, a window will be shown.") 378*d9f75844SAndroid Build Coastguard Worker parser.add_argument( 379*d9f75844SAndroid Build Coastguard Worker "files", 380*d9f75844SAndroid Build Coastguard Worker nargs="+", 381*d9f75844SAndroid Build Coastguard Worker action=CustomAction, 382*d9f75844SAndroid Build Coastguard Worker help="List of text-based files generated by loopback tests.") 383*d9f75844SAndroid Build Coastguard Worker return parser 384*d9f75844SAndroid Build Coastguard Worker 385*d9f75844SAndroid Build Coastguard Worker 386*d9f75844SAndroid Build Coastguard Workerdef _PlotConfigFromArgs(args, graph_num): 387*d9f75844SAndroid Build Coastguard Worker # Pylint complains about using kwargs, so have to do it this way. 388*d9f75844SAndroid Build Coastguard Worker cycle_length = None 389*d9f75844SAndroid Build Coastguard Worker frames = None 390*d9f75844SAndroid Build Coastguard Worker offset = 0 391*d9f75844SAndroid Build Coastguard Worker output_filename = None 392*d9f75844SAndroid Build Coastguard Worker title = "Graph" 393*d9f75844SAndroid Build Coastguard Worker 394*d9f75844SAndroid Build Coastguard Worker fields = [] 395*d9f75844SAndroid Build Coastguard Worker files = [] 396*d9f75844SAndroid Build Coastguard Worker mask = 0 397*d9f75844SAndroid Build Coastguard Worker for key, values in args: 398*d9f75844SAndroid Build Coastguard Worker if key == "cycle_length": 399*d9f75844SAndroid Build Coastguard Worker cycle_length = values[0] 400*d9f75844SAndroid Build Coastguard Worker elif key == "frames": 401*d9f75844SAndroid Build Coastguard Worker frames = values[0] 402*d9f75844SAndroid Build Coastguard Worker elif key == "offset": 403*d9f75844SAndroid Build Coastguard Worker offset = values[0] 404*d9f75844SAndroid Build Coastguard Worker elif key == "output_filename": 405*d9f75844SAndroid Build Coastguard Worker output_filename = values[0] 406*d9f75844SAndroid Build Coastguard Worker elif key == "title": 407*d9f75844SAndroid Build Coastguard Worker title = values[0] 408*d9f75844SAndroid Build Coastguard Worker elif key == "drop": 409*d9f75844SAndroid Build Coastguard Worker mask |= HIDE_DROPPED 410*d9f75844SAndroid Build Coastguard Worker elif key == "right": 411*d9f75844SAndroid Build Coastguard Worker mask |= RIGHT_Y_AXIS 412*d9f75844SAndroid Build Coastguard Worker elif key == "field": 413*d9f75844SAndroid Build Coastguard Worker field_id = FieldArgToId(values[0]) 414*d9f75844SAndroid Build Coastguard Worker fields.append(field_id | mask if field_id is not None else None) 415*d9f75844SAndroid Build Coastguard Worker mask = 0 # Reset mask after the field argument. 416*d9f75844SAndroid Build Coastguard Worker elif key == "files": 417*d9f75844SAndroid Build Coastguard Worker files.extend(values) 418*d9f75844SAndroid Build Coastguard Worker 419*d9f75844SAndroid Build Coastguard Worker if not files: 420*d9f75844SAndroid Build Coastguard Worker raise Exception( 421*d9f75844SAndroid Build Coastguard Worker "Missing file argument(s) for graph #{}".format(graph_num)) 422*d9f75844SAndroid Build Coastguard Worker if not fields: 423*d9f75844SAndroid Build Coastguard Worker raise Exception( 424*d9f75844SAndroid Build Coastguard Worker "Missing field argument(s) for graph #{}".format(graph_num)) 425*d9f75844SAndroid Build Coastguard Worker 426*d9f75844SAndroid Build Coastguard Worker return PlotConfig(fields, 427*d9f75844SAndroid Build Coastguard Worker LoadFiles(files), 428*d9f75844SAndroid Build Coastguard Worker cycle_length=cycle_length, 429*d9f75844SAndroid Build Coastguard Worker frames=frames, 430*d9f75844SAndroid Build Coastguard Worker offset=offset, 431*d9f75844SAndroid Build Coastguard Worker output_filename=output_filename, 432*d9f75844SAndroid Build Coastguard Worker title=title) 433*d9f75844SAndroid Build Coastguard Worker 434*d9f75844SAndroid Build Coastguard Worker 435*d9f75844SAndroid Build Coastguard Workerdef PlotConfigsFromArgs(args): 436*d9f75844SAndroid Build Coastguard Worker """Generates plot configs for given command line arguments.""" 437*d9f75844SAndroid Build Coastguard Worker # The way it works: 438*d9f75844SAndroid Build Coastguard Worker # First we detect separators -n/--next and split arguments into groups, one 439*d9f75844SAndroid Build Coastguard Worker # for each plot. For each group, we partially parse it with 440*d9f75844SAndroid Build Coastguard Worker # argparse.ArgumentParser, modified to remember the order of arguments. 441*d9f75844SAndroid Build Coastguard Worker # Then we traverse the argument list and fill the PlotConfig. 442*d9f75844SAndroid Build Coastguard Worker args = itertools.groupby(args, lambda x: x in ["-n", "--next"]) 443*d9f75844SAndroid Build Coastguard Worker prep_args = list(list(group) for match, group in args if not match) 444*d9f75844SAndroid Build Coastguard Worker 445*d9f75844SAndroid Build Coastguard Worker parser = GetParser() 446*d9f75844SAndroid Build Coastguard Worker plot_configs = [] 447*d9f75844SAndroid Build Coastguard Worker for index, raw_args in enumerate(prep_args): 448*d9f75844SAndroid Build Coastguard Worker graph_args = parser.parse_args(raw_args).ordered_args 449*d9f75844SAndroid Build Coastguard Worker plot_configs.append(_PlotConfigFromArgs(graph_args, index)) 450*d9f75844SAndroid Build Coastguard Worker return plot_configs 451*d9f75844SAndroid Build Coastguard Worker 452*d9f75844SAndroid Build Coastguard Worker 453*d9f75844SAndroid Build Coastguard Workerdef ShowOrSavePlots(plot_configs): 454*d9f75844SAndroid Build Coastguard Worker for config in plot_configs: 455*d9f75844SAndroid Build Coastguard Worker fig = plt.figure(figsize=(14.0, 10.0)) 456*d9f75844SAndroid Build Coastguard Worker ax = fig.add_subPlot(1, 1, 1) 457*d9f75844SAndroid Build Coastguard Worker 458*d9f75844SAndroid Build Coastguard Worker plt.title(config.title) 459*d9f75844SAndroid Build Coastguard Worker config.Plot(ax) 460*d9f75844SAndroid Build Coastguard Worker if config.output_filename: 461*d9f75844SAndroid Build Coastguard Worker print "Saving to", config.output_filename 462*d9f75844SAndroid Build Coastguard Worker fig.savefig(config.output_filename) 463*d9f75844SAndroid Build Coastguard Worker plt.close(fig) 464*d9f75844SAndroid Build Coastguard Worker 465*d9f75844SAndroid Build Coastguard Worker plt.show() 466*d9f75844SAndroid Build Coastguard Worker 467*d9f75844SAndroid Build Coastguard Worker 468*d9f75844SAndroid Build Coastguard Workerif __name__ == "__main__": 469*d9f75844SAndroid Build Coastguard Worker ShowOrSavePlots(PlotConfigsFromArgs(sys.argv[1:])) 470