xref: /aosp_15_r20/external/webrtc/video/full_stack_tests_plot.py (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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