xref: /aosp_15_r20/cts/tools/cts-media/get_achievable_rates.py (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1*b7c941bbSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*b7c941bbSAndroid Build Coastguard Worker# Copyright (C) 2015 The Android Open Source Project
3*b7c941bbSAndroid Build Coastguard Worker#
4*b7c941bbSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
5*b7c941bbSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
6*b7c941bbSAndroid Build Coastguard Worker# You may obtain a copy of the License at
7*b7c941bbSAndroid Build Coastguard Worker#
8*b7c941bbSAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
9*b7c941bbSAndroid Build Coastguard Worker#
10*b7c941bbSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*b7c941bbSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
12*b7c941bbSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*b7c941bbSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
14*b7c941bbSAndroid Build Coastguard Worker# limitations under the License.
15*b7c941bbSAndroid Build Coastguard Worker#
16*b7c941bbSAndroid Build Coastguard Worker
17*b7c941bbSAndroid Build Coastguard Workerimport argparse, json, math, re, sys, zipfile
18*b7c941bbSAndroid Build Coastguard Workerimport xml.etree.ElementTree as ET
19*b7c941bbSAndroid Build Coastguard Workerfrom collections import defaultdict, namedtuple
20*b7c941bbSAndroid Build Coastguard Worker
21*b7c941bbSAndroid Build Coastguard Worker
22*b7c941bbSAndroid Build Coastguard Workerclass Size(namedtuple('Size', ['width', 'height'])):
23*b7c941bbSAndroid Build Coastguard Worker  """A namedtuple with width and height fields."""
24*b7c941bbSAndroid Build Coastguard Worker  def __str__(self):
25*b7c941bbSAndroid Build Coastguard Worker    return '%dx%d' % (self.width, self.height)
26*b7c941bbSAndroid Build Coastguard Worker
27*b7c941bbSAndroid Build Coastguard Workerdef nicekey(v):
28*b7c941bbSAndroid Build Coastguard Worker  """Returns a nicer sort key for sorting strings.
29*b7c941bbSAndroid Build Coastguard Worker
30*b7c941bbSAndroid Build Coastguard Worker  This sorts using lower case, with numbers in numerical order first."""
31*b7c941bbSAndroid Build Coastguard Worker  key = []
32*b7c941bbSAndroid Build Coastguard Worker  num = False
33*b7c941bbSAndroid Build Coastguard Worker  for p in re.split('(\d+)', v.lower()):
34*b7c941bbSAndroid Build Coastguard Worker    if num:
35*b7c941bbSAndroid Build Coastguard Worker      key.append(('0', int(p)))
36*b7c941bbSAndroid Build Coastguard Worker    elif p:
37*b7c941bbSAndroid Build Coastguard Worker      key.append((p, 0))
38*b7c941bbSAndroid Build Coastguard Worker    num = not num
39*b7c941bbSAndroid Build Coastguard Worker  return key + [(v, 0)]
40*b7c941bbSAndroid Build Coastguard Worker
41*b7c941bbSAndroid Build Coastguard Workerdef nice(v):
42*b7c941bbSAndroid Build Coastguard Worker  """Returns a nicer representation for objects in debug messages.
43*b7c941bbSAndroid Build Coastguard Worker
44*b7c941bbSAndroid Build Coastguard Worker  Dictionaries are sorted, size is WxH, unicode removed, and floats have 1 digit precision."""
45*b7c941bbSAndroid Build Coastguard Worker  if isinstance(v, dict):
46*b7c941bbSAndroid Build Coastguard Worker    return 'dict(' + ', '.join(k + '=' + nice(v) for k, v in sorted(v.items(), key=lambda i: nicekey(i[0]))) + ')'
47*b7c941bbSAndroid Build Coastguard Worker  if isinstance(v, str):
48*b7c941bbSAndroid Build Coastguard Worker    return repr(v)
49*b7c941bbSAndroid Build Coastguard Worker  if isinstance(v, int):
50*b7c941bbSAndroid Build Coastguard Worker    return str(v)
51*b7c941bbSAndroid Build Coastguard Worker  if isinstance(v, Size):
52*b7c941bbSAndroid Build Coastguard Worker    return repr(str(v))
53*b7c941bbSAndroid Build Coastguard Worker  if isinstance(v, float):
54*b7c941bbSAndroid Build Coastguard Worker    return '%.1f' % v
55*b7c941bbSAndroid Build Coastguard Worker  if isinstance(v, type(u'')):
56*b7c941bbSAndroid Build Coastguard Worker    return repr(str(v))
57*b7c941bbSAndroid Build Coastguard Worker  raise ValueError(v)
58*b7c941bbSAndroid Build Coastguard Worker
59*b7c941bbSAndroid Build Coastguard Workerclass ResultParser:
60*b7c941bbSAndroid Build Coastguard Worker  @staticmethod
61*b7c941bbSAndroid Build Coastguard Worker  def _intify(value):
62*b7c941bbSAndroid Build Coastguard Worker    """Returns a value converted to int if possible, else the original value."""
63*b7c941bbSAndroid Build Coastguard Worker    try:
64*b7c941bbSAndroid Build Coastguard Worker      return int(value)
65*b7c941bbSAndroid Build Coastguard Worker    except ValueError:
66*b7c941bbSAndroid Build Coastguard Worker      return value
67*b7c941bbSAndroid Build Coastguard Worker
68*b7c941bbSAndroid Build Coastguard Worker  def _parseDict(self, value):
69*b7c941bbSAndroid Build Coastguard Worker    """Parses a MediaFormat from its string representation sans brackets."""
70*b7c941bbSAndroid Build Coastguard Worker    return dict((k, self._intify(v))
71*b7c941bbSAndroid Build Coastguard Worker                for k, v in re.findall(r'([^ =]+)=([^ [=]+(?:|\[[^\]]+\]))(?:, |$)', value))
72*b7c941bbSAndroid Build Coastguard Worker
73*b7c941bbSAndroid Build Coastguard Worker  def _cleanFormat(self, format):
74*b7c941bbSAndroid Build Coastguard Worker    """Removes internal fields from a parsed MediaFormat."""
75*b7c941bbSAndroid Build Coastguard Worker    format.pop('what', None)
76*b7c941bbSAndroid Build Coastguard Worker    format.pop('image-data', None)
77*b7c941bbSAndroid Build Coastguard Worker
78*b7c941bbSAndroid Build Coastguard Worker  MESSAGE_PATTERN = r'(?P<key>\w+)=(?P<value>\{[^}]*\}|[^ ,{}]+)'
79*b7c941bbSAndroid Build Coastguard Worker
80*b7c941bbSAndroid Build Coastguard Worker  def _parsePartialResult(self, message_match):
81*b7c941bbSAndroid Build Coastguard Worker    """Parses a partial test result conforming to the message pattern.
82*b7c941bbSAndroid Build Coastguard Worker
83*b7c941bbSAndroid Build Coastguard Worker    Returns:
84*b7c941bbSAndroid Build Coastguard Worker      A tuple of string key and int, string or dict value, where dict has
85*b7c941bbSAndroid Build Coastguard Worker      string keys mapping to int or string values.
86*b7c941bbSAndroid Build Coastguard Worker    """
87*b7c941bbSAndroid Build Coastguard Worker    key, value = message_match.group('key', 'value')
88*b7c941bbSAndroid Build Coastguard Worker    if value.startswith('{'):
89*b7c941bbSAndroid Build Coastguard Worker      value = self._parseDict(value[1:-1])
90*b7c941bbSAndroid Build Coastguard Worker      if key.endswith('Format'):
91*b7c941bbSAndroid Build Coastguard Worker        self._cleanFormat(value)
92*b7c941bbSAndroid Build Coastguard Worker    else:
93*b7c941bbSAndroid Build Coastguard Worker      value = self._intify(value)
94*b7c941bbSAndroid Build Coastguard Worker    return key, value
95*b7c941bbSAndroid Build Coastguard Worker
96*b7c941bbSAndroid Build Coastguard Worker
97*b7c941bbSAndroid Build Coastguard Workerdef perc(data, p, fn=round):
98*b7c941bbSAndroid Build Coastguard Worker  """Returns a percentile value from a sorted array.
99*b7c941bbSAndroid Build Coastguard Worker
100*b7c941bbSAndroid Build Coastguard Worker  Arguments:
101*b7c941bbSAndroid Build Coastguard Worker    data: sorted data
102*b7c941bbSAndroid Build Coastguard Worker    p:    percentile value (0-100)
103*b7c941bbSAndroid Build Coastguard Worker    fn:   method used for rounding the percentile to an integer index in data
104*b7c941bbSAndroid Build Coastguard Worker  """
105*b7c941bbSAndroid Build Coastguard Worker  return data[int(fn((len(data) - 1) * p / 100))]
106*b7c941bbSAndroid Build Coastguard Worker
107*b7c941bbSAndroid Build Coastguard Worker
108*b7c941bbSAndroid Build Coastguard Workerdef genXml(data, A=None):
109*b7c941bbSAndroid Build Coastguard Worker  yield '<?xml version="1.0" encoding="utf-8" ?>'
110*b7c941bbSAndroid Build Coastguard Worker  yield '<!-- Copyright 2016 The Android Open Source Project'
111*b7c941bbSAndroid Build Coastguard Worker  yield ''
112*b7c941bbSAndroid Build Coastguard Worker  yield '     Licensed under the Apache License, Version 2.0 (the "License");'
113*b7c941bbSAndroid Build Coastguard Worker  yield '     you may not use this file except in compliance with the License.'
114*b7c941bbSAndroid Build Coastguard Worker  yield '     You may obtain a copy of the License at'
115*b7c941bbSAndroid Build Coastguard Worker  yield ''
116*b7c941bbSAndroid Build Coastguard Worker  yield '          http://www.apache.org/licenses/LICENSE-2.0'
117*b7c941bbSAndroid Build Coastguard Worker  yield ''
118*b7c941bbSAndroid Build Coastguard Worker  yield '     Unless required by applicable law or agreed to in writing, software'
119*b7c941bbSAndroid Build Coastguard Worker  yield '     distributed under the License is distributed on an "AS IS" BASIS,'
120*b7c941bbSAndroid Build Coastguard Worker  yield '     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.'
121*b7c941bbSAndroid Build Coastguard Worker  yield '     See the License for the specific language governing permissions and'
122*b7c941bbSAndroid Build Coastguard Worker  yield '     limitations under the License.'
123*b7c941bbSAndroid Build Coastguard Worker  yield '-->'
124*b7c941bbSAndroid Build Coastguard Worker  yield ''
125*b7c941bbSAndroid Build Coastguard Worker  yield '<MediaCodecs>'
126*b7c941bbSAndroid Build Coastguard Worker  last_section = None
127*b7c941bbSAndroid Build Coastguard Worker  from collections import namedtuple
128*b7c941bbSAndroid Build Coastguard Worker  Comp = namedtuple('Comp', 'is_decoder google mime name')
129*b7c941bbSAndroid Build Coastguard Worker  Result = namedtuple('Result', 'mn mx p95 med geo p5')
130*b7c941bbSAndroid Build Coastguard Worker  for comp_, cdata in sorted(data.items()):
131*b7c941bbSAndroid Build Coastguard Worker    comp = Comp(*comp_)
132*b7c941bbSAndroid Build Coastguard Worker    section = 'Decoders' if comp.is_decoder else 'Encoders'
133*b7c941bbSAndroid Build Coastguard Worker    if section != last_section:
134*b7c941bbSAndroid Build Coastguard Worker      if last_section:
135*b7c941bbSAndroid Build Coastguard Worker        yield '    </%s>' % last_section
136*b7c941bbSAndroid Build Coastguard Worker      yield '    <%s>' % section
137*b7c941bbSAndroid Build Coastguard Worker      last_section = section
138*b7c941bbSAndroid Build Coastguard Worker    yield '        <MediaCodec name="%s" type="%s" update="true">' % (comp.name, comp.mime)
139*b7c941bbSAndroid Build Coastguard Worker    for size, sdata in sorted(cdata.items()):
140*b7c941bbSAndroid Build Coastguard Worker      data = sorted(sdata)
141*b7c941bbSAndroid Build Coastguard Worker      N = len(data)
142*b7c941bbSAndroid Build Coastguard Worker      mn, mx = data[0], data[-1]
143*b7c941bbSAndroid Build Coastguard Worker
144*b7c941bbSAndroid Build Coastguard Worker      if N < 20 and not A.ignore:
145*b7c941bbSAndroid Build Coastguard Worker        raise ValueError("need at least 20 data points for %s size %s; have %s" %
146*b7c941bbSAndroid Build Coastguard Worker                         (comp.name, size, N))
147*b7c941bbSAndroid Build Coastguard Worker
148*b7c941bbSAndroid Build Coastguard Worker      TO = 2.2     # tolerance with margin
149*b7c941bbSAndroid Build Coastguard Worker      T = TO / 1.1 # tolerance without margin
150*b7c941bbSAndroid Build Coastguard Worker
151*b7c941bbSAndroid Build Coastguard Worker      Final = namedtuple('Final', 'comment c2 var qual')
152*b7c941bbSAndroid Build Coastguard Worker      lastFinal = None
153*b7c941bbSAndroid Build Coastguard Worker      for RG in (10, 15, 20, 25, 30, 40, 50):
154*b7c941bbSAndroid Build Coastguard Worker        P = 50./RG
155*b7c941bbSAndroid Build Coastguard Worker        quality = 0
156*b7c941bbSAndroid Build Coastguard Worker        p95, med, p5 = perc(data, P, math.floor), perc(data, 50, round), perc(data, 100 - P, math.ceil)
157*b7c941bbSAndroid Build Coastguard Worker        geo = math.sqrt(p5 * p95)
158*b7c941bbSAndroid Build Coastguard Worker        comment = ''
159*b7c941bbSAndroid Build Coastguard Worker        pub_lo, pub_hi = min(int(p95 * T), round(geo)), max(math.ceil(p5 / T), round(geo))
160*b7c941bbSAndroid Build Coastguard Worker        if pub_lo > med:
161*b7c941bbSAndroid Build Coastguard Worker          if pub_lo > med * 1.1:
162*b7c941bbSAndroid Build Coastguard Worker            quality += 0.5
163*b7c941bbSAndroid Build Coastguard Worker            comment += ' SLOW'
164*b7c941bbSAndroid Build Coastguard Worker          pub_lo = int(med)
165*b7c941bbSAndroid Build Coastguard Worker        if N < 2 * RG:
166*b7c941bbSAndroid Build Coastguard Worker          comment += ' N=%d' % N
167*b7c941bbSAndroid Build Coastguard Worker          quality += 2
168*b7c941bbSAndroid Build Coastguard Worker        RGVAR = False
169*b7c941bbSAndroid Build Coastguard Worker        if p5 / p95 > T ** 3:
170*b7c941bbSAndroid Build Coastguard Worker          quality += 3
171*b7c941bbSAndroid Build Coastguard Worker          RGVAR = True
172*b7c941bbSAndroid Build Coastguard Worker          if pub_hi > pub_lo * TO:
173*b7c941bbSAndroid Build Coastguard Worker            quality += 1
174*b7c941bbSAndroid Build Coastguard Worker            if RG == 10:
175*b7c941bbSAndroid Build Coastguard Worker              # find best pub_lo and pub_hi
176*b7c941bbSAndroid Build Coastguard Worker              for i in range(N // 2):
177*b7c941bbSAndroid Build Coastguard Worker                pub_lo_, pub_hi_ = min(int(data[N // 2 - i - 1] * T), round(geo), int(med)), max(math.ceil(data[N // 2 + i] / T), round(geo))
178*b7c941bbSAndroid Build Coastguard Worker                if pub_hi_ > pub_lo_ * TO:
179*b7c941bbSAndroid Build Coastguard Worker                  # ???
180*b7c941bbSAndroid Build Coastguard Worker                  pub_lo = min(pub_lo, math.ceil(pub_hi_ / TO))
181*b7c941bbSAndroid Build Coastguard Worker                  break
182*b7c941bbSAndroid Build Coastguard Worker                pub_lo, pub_hi = pub_lo_, pub_hi_
183*b7c941bbSAndroid Build Coastguard Worker        if mn < pub_lo / T or mx > pub_hi * T or pub_lo <= pub_hi / T:
184*b7c941bbSAndroid Build Coastguard Worker          quality += 1
185*b7c941bbSAndroid Build Coastguard Worker          comment += ' FLAKY('
186*b7c941bbSAndroid Build Coastguard Worker          if round(mn, 1) < pub_lo / T:
187*b7c941bbSAndroid Build Coastguard Worker            comment += 'mn=%.1f < ' % mn
188*b7c941bbSAndroid Build Coastguard Worker          comment += 'RANGE'
189*b7c941bbSAndroid Build Coastguard Worker          if round(mx, 1) > pub_hi * T:
190*b7c941bbSAndroid Build Coastguard Worker            comment += ' < mx=%.1f' % mx
191*b7c941bbSAndroid Build Coastguard Worker          comment += ')'
192*b7c941bbSAndroid Build Coastguard Worker        if False:
193*b7c941bbSAndroid Build Coastguard Worker          comment += ' DATA(mn=%1.f p%d=%1.f accept=%1.f-%1.f p50=%1.f p%d=%1.f mx=%1.f)' % (
194*b7c941bbSAndroid Build Coastguard Worker            mn, 100-P, p95, pub_lo / T, pub_hi * T, med, P, p5, mx)
195*b7c941bbSAndroid Build Coastguard Worker        var = math.sqrt(p5/p95)
196*b7c941bbSAndroid Build Coastguard Worker        if p95 < geo / T or p5 > geo * T:
197*b7c941bbSAndroid Build Coastguard Worker          if RGVAR:
198*b7c941bbSAndroid Build Coastguard Worker            comment += ' RG.VARIANCE:%.1f' % ((p5/p95) ** (1./3))
199*b7c941bbSAndroid Build Coastguard Worker          else:
200*b7c941bbSAndroid Build Coastguard Worker            comment += ' variance:%.1f' % var
201*b7c941bbSAndroid Build Coastguard Worker        comment = comment.replace('RANGE', '%d - %d' % (math.ceil(pub_lo / T), int(pub_hi * T)))
202*b7c941bbSAndroid Build Coastguard Worker        c2 = ''
203*b7c941bbSAndroid Build Coastguard Worker        if N >= 2 * RG:
204*b7c941bbSAndroid Build Coastguard Worker          c2 += ' N=%d' % N
205*b7c941bbSAndroid Build Coastguard Worker        if var <= T or p5 / p95 > T ** 3:
206*b7c941bbSAndroid Build Coastguard Worker          c2 += ' v%d%%=%.1f' % (round(100 - 2 * P), var)
207*b7c941bbSAndroid Build Coastguard Worker        if A and A.dbg:
208*b7c941bbSAndroid Build Coastguard Worker          c2 += ' E=%s' % (str(quality))
209*b7c941bbSAndroid Build Coastguard Worker        if c2:
210*b7c941bbSAndroid Build Coastguard Worker          c2 = ' <!--%s -->' % c2
211*b7c941bbSAndroid Build Coastguard Worker
212*b7c941bbSAndroid Build Coastguard Worker        if comment:
213*b7c941bbSAndroid Build Coastguard Worker          comment = '            <!-- measured %d%%:%d-%d med:%d%s -->' % (round(100 - 2 * P), int(p95), math.ceil(p5), int(round(med)), comment)
214*b7c941bbSAndroid Build Coastguard Worker        if A and A.dbg: yield '<!-- --> %s%s' % (comment, c2)
215*b7c941bbSAndroid Build Coastguard Worker        c2 = '            <Limit name="measured-frame-rate-%s" range="%d-%d" />%s' % (size, pub_lo, pub_hi, c2)
216*b7c941bbSAndroid Build Coastguard Worker        final = Final(comment, c2, var, quality)
217*b7c941bbSAndroid Build Coastguard Worker        if lastFinal and final.var > lastFinal.var * math.sqrt(1.3):
218*b7c941bbSAndroid Build Coastguard Worker          if A and A.dbg: yield '<!-- RANGE JUMP -->'
219*b7c941bbSAndroid Build Coastguard Worker          break
220*b7c941bbSAndroid Build Coastguard Worker        elif not lastFinal or quality <= lastFinal.qual:
221*b7c941bbSAndroid Build Coastguard Worker          lastFinal = final
222*b7c941bbSAndroid Build Coastguard Worker        if N < 2 * RG or quality >= 4:
223*b7c941bbSAndroid Build Coastguard Worker          break
224*b7c941bbSAndroid Build Coastguard Worker      comment, c2, var, quality = lastFinal
225*b7c941bbSAndroid Build Coastguard Worker
226*b7c941bbSAndroid Build Coastguard Worker      if comment:
227*b7c941bbSAndroid Build Coastguard Worker        yield comment
228*b7c941bbSAndroid Build Coastguard Worker      yield c2
229*b7c941bbSAndroid Build Coastguard Worker    yield '        </MediaCodec>'
230*b7c941bbSAndroid Build Coastguard Worker  if last_section:
231*b7c941bbSAndroid Build Coastguard Worker    yield '    </%s>' % last_section
232*b7c941bbSAndroid Build Coastguard Worker  yield '</MediaCodecs>'
233*b7c941bbSAndroid Build Coastguard Worker
234*b7c941bbSAndroid Build Coastguard Worker
235*b7c941bbSAndroid Build Coastguard Workerclass Data:
236*b7c941bbSAndroid Build Coastguard Worker  def __init__(self):
237*b7c941bbSAndroid Build Coastguard Worker    self.data = set()
238*b7c941bbSAndroid Build Coastguard Worker    self.kind = {}
239*b7c941bbSAndroid Build Coastguard Worker    self.devices = set()
240*b7c941bbSAndroid Build Coastguard Worker    self.parser = ResultParser()
241*b7c941bbSAndroid Build Coastguard Worker
242*b7c941bbSAndroid Build Coastguard Worker  def summarize(self, A=None):
243*b7c941bbSAndroid Build Coastguard Worker    devs = sorted(self.devices)
244*b7c941bbSAndroid Build Coastguard Worker    #           device  > (not encoder,goog,mime,codec)  >  size > fps
245*b7c941bbSAndroid Build Coastguard Worker    xmlInfo = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
246*b7c941bbSAndroid Build Coastguard Worker
247*b7c941bbSAndroid Build Coastguard Worker    for mime, encoder, goog in sorted(set(self.kind.values())):
248*b7c941bbSAndroid Build Coastguard Worker      for dev, build, codec, size, num, std, avg, p0, p5, p10, p20, p30, p40, p50, p60, p70, p80, p90, p95, p100 in self.data:
249*b7c941bbSAndroid Build Coastguard Worker        if self.kind[codec] != (mime, encoder, goog):
250*b7c941bbSAndroid Build Coastguard Worker          continue
251*b7c941bbSAndroid Build Coastguard Worker
252*b7c941bbSAndroid Build Coastguard Worker        if p95 > 2: # ignore measurements at or below 2fps
253*b7c941bbSAndroid Build Coastguard Worker          xmlInfo[dev][(not encoder, goog, mime, codec)][size].append(p95)
254*b7c941bbSAndroid Build Coastguard Worker        else:
255*b7c941bbSAndroid Build Coastguard Worker          print("warning: p95 value is suspiciously low: {}".format(
256*b7c941bbSAndroid Build Coastguard Worker            nice(dict(config=dict(dev=dev, codec=codec, size=str(size), N=num),
257*b7c941bbSAndroid Build Coastguard Worker                 data=dict(std=std, avg=avg, p0=p0, p5=p5, p10=p10, p20=p20, p30=p30, p40=p40,
258*b7c941bbSAndroid Build Coastguard Worker                           p50=p50, p60=p60, p70=p70, p80=p80, p90=p90, p95=p95, p100=p100)))),
259*b7c941bbSAndroid Build Coastguard Worker            file=sys.stderr)
260*b7c941bbSAndroid Build Coastguard Worker    for dev, ddata in xmlInfo.items():
261*b7c941bbSAndroid Build Coastguard Worker      outFile = '{}.media_codecs_performance.xml'.format(dev)
262*b7c941bbSAndroid Build Coastguard Worker      print(f"generating {outFile}", file=sys.stderr)
263*b7c941bbSAndroid Build Coastguard Worker      with open(outFile, "wt") as out:
264*b7c941bbSAndroid Build Coastguard Worker        for l in genXml(ddata, A=A):
265*b7c941bbSAndroid Build Coastguard Worker          out.write(l + '\n')
266*b7c941bbSAndroid Build Coastguard Worker          print(l)
267*b7c941bbSAndroid Build Coastguard Worker      print(f"generated {outFile}", file=sys.stderr)
268*b7c941bbSAndroid Build Coastguard Worker
269*b7c941bbSAndroid Build Coastguard Worker  def parse_fmt(self, fmt):
270*b7c941bbSAndroid Build Coastguard Worker    return self.parser._parseDict(fmt)
271*b7c941bbSAndroid Build Coastguard Worker
272*b7c941bbSAndroid Build Coastguard Worker  def parse_perf(self, a, device, build):
273*b7c941bbSAndroid Build Coastguard Worker    def rateFn(i):
274*b7c941bbSAndroid Build Coastguard Worker      if i is None:
275*b7c941bbSAndroid Build Coastguard Worker        return i
276*b7c941bbSAndroid Build Coastguard Worker      elif i == 0:
277*b7c941bbSAndroid Build Coastguard Worker        return 1e6
278*b7c941bbSAndroid Build Coastguard Worker      return 1000. / i
279*b7c941bbSAndroid Build Coastguard Worker
280*b7c941bbSAndroid Build Coastguard Worker    points = ('avg', 'min', 'p5', 'p10', 'p20', 'p30', 'p40', 'p50', 'p60', 'p70', 'p80', 'p90', 'p95', 'max')
281*b7c941bbSAndroid Build Coastguard Worker    a = dict(a)
282*b7c941bbSAndroid Build Coastguard Worker    codec = a['codec_name'] + ''
283*b7c941bbSAndroid Build Coastguard Worker    mime = a['mime_type']
284*b7c941bbSAndroid Build Coastguard Worker    size = Size(a['width'], a['height'])
285*b7c941bbSAndroid Build Coastguard Worker    if 'decode_to' in a:
286*b7c941bbSAndroid Build Coastguard Worker      fmt = self.parse_fmt(a['output_format'])
287*b7c941bbSAndroid Build Coastguard Worker      ofmt = self.parse_fmt(a['input_format'])
288*b7c941bbSAndroid Build Coastguard Worker    else:
289*b7c941bbSAndroid Build Coastguard Worker      fmt = self.parse_fmt(a['input_format'])
290*b7c941bbSAndroid Build Coastguard Worker      ofmt = self.parse_fmt(a['output_format'])
291*b7c941bbSAndroid Build Coastguard Worker    size = Size(max(fmt['width'], ofmt['width']), max(fmt['height'], ofmt['height']))
292*b7c941bbSAndroid Build Coastguard Worker
293*b7c941bbSAndroid Build Coastguard Worker    try:
294*b7c941bbSAndroid Build Coastguard Worker      prefix = 'time_avg_stats_'
295*b7c941bbSAndroid Build Coastguard Worker      if prefix + 'stdev' in a and a[prefix + 'avg']:
296*b7c941bbSAndroid Build Coastguard Worker        stdev = (a[prefix + 'stdev'] * 1e3 / a[prefix + 'avg'] ** 2)
297*b7c941bbSAndroid Build Coastguard Worker        data = ((device, build, codec, size, a[prefix  + 'num'], stdev) +
298*b7c941bbSAndroid Build Coastguard Worker                tuple(rateFn(a.get(prefix + i)) for i in points))
299*b7c941bbSAndroid Build Coastguard Worker        self.data.add(data)
300*b7c941bbSAndroid Build Coastguard Worker        self.kind[codec] = (mime, 'decode_to' not in a, codec.lower().startswith('omx.google.'))
301*b7c941bbSAndroid Build Coastguard Worker        self.devices.add(data[0])
302*b7c941bbSAndroid Build Coastguard Worker    except (KeyError, ZeroDivisionError):
303*b7c941bbSAndroid Build Coastguard Worker      print(a, file=sys.stderr)
304*b7c941bbSAndroid Build Coastguard Worker      raise
305*b7c941bbSAndroid Build Coastguard Worker
306*b7c941bbSAndroid Build Coastguard Worker  def parse_json(self, json, device, build):
307*b7c941bbSAndroid Build Coastguard Worker    for test, results in json:
308*b7c941bbSAndroid Build Coastguard Worker      if test in ("video_encoder_performance", "video_decoder_performance"):
309*b7c941bbSAndroid Build Coastguard Worker        try:
310*b7c941bbSAndroid Build Coastguard Worker          if isinstance(results, list) and len(results[0]) and len(results[0][0]) == 2 and len(results[0][0][0]):
311*b7c941bbSAndroid Build Coastguard Worker            for result in results:
312*b7c941bbSAndroid Build Coastguard Worker              self.parse_perf(result, device, build)
313*b7c941bbSAndroid Build Coastguard Worker          else:
314*b7c941bbSAndroid Build Coastguard Worker            self.parse_perf(results, device, build)
315*b7c941bbSAndroid Build Coastguard Worker        except KeyboardInterrupt:
316*b7c941bbSAndroid Build Coastguard Worker          raise
317*b7c941bbSAndroid Build Coastguard Worker
318*b7c941bbSAndroid Build Coastguard Worker  def parse_result(self, result):
319*b7c941bbSAndroid Build Coastguard Worker    device, build = '', ''
320*b7c941bbSAndroid Build Coastguard Worker    if not result.endswith('.zip'):
321*b7c941bbSAndroid Build Coastguard Worker      print(f"cannot parse %{result}", file=sys.stderr)
322*b7c941bbSAndroid Build Coastguard Worker      return
323*b7c941bbSAndroid Build Coastguard Worker
324*b7c941bbSAndroid Build Coastguard Worker    try:
325*b7c941bbSAndroid Build Coastguard Worker      with zipfile.ZipFile(result) as zip:
326*b7c941bbSAndroid Build Coastguard Worker        resultInfo, testInfos = None, []
327*b7c941bbSAndroid Build Coastguard Worker        for info in zip.infolist():
328*b7c941bbSAndroid Build Coastguard Worker          if re.search(r'/GenericDeviceInfo.deviceinfo.json$', info.filename):
329*b7c941bbSAndroid Build Coastguard Worker            resultInfo = info
330*b7c941bbSAndroid Build Coastguard Worker          elif re.search(r'/Cts(Media|Video)(Decoder)?TestCases\.reportlog\.json$', info.filename):
331*b7c941bbSAndroid Build Coastguard Worker            testInfos.append(info)
332*b7c941bbSAndroid Build Coastguard Worker        if resultInfo:
333*b7c941bbSAndroid Build Coastguard Worker          try:
334*b7c941bbSAndroid Build Coastguard Worker            jsonFile = zip.open(resultInfo)
335*b7c941bbSAndroid Build Coastguard Worker            jsonData = json.load(jsonFile)
336*b7c941bbSAndroid Build Coastguard Worker            device, build = jsonData['build_device'], jsonData['build_id']
337*b7c941bbSAndroid Build Coastguard Worker          except ValueError:
338*b7c941bbSAndroid Build Coastguard Worker            print(f"could not parse %{resultInfo.filename}", file=sys.stderr)
339*b7c941bbSAndroid Build Coastguard Worker        for info in testInfos:
340*b7c941bbSAndroid Build Coastguard Worker          jsonFile = zip.open(info)
341*b7c941bbSAndroid Build Coastguard Worker          try:
342*b7c941bbSAndroid Build Coastguard Worker            jsonData = json.load(jsonFile, object_pairs_hook=lambda items: items)
343*b7c941bbSAndroid Build Coastguard Worker          except ValueError:
344*b7c941bbSAndroid Build Coastguard Worker            print(f"cannot parse JSON in {info.filename}", file=sys.stderr)
345*b7c941bbSAndroid Build Coastguard Worker          self.parse_json(jsonData, device, build)
346*b7c941bbSAndroid Build Coastguard Worker
347*b7c941bbSAndroid Build Coastguard Worker    except zipfile.BadZipfile:
348*b7c941bbSAndroid Build Coastguard Worker      raise ValueError('bad zipfile')
349*b7c941bbSAndroid Build Coastguard Worker
350*b7c941bbSAndroid Build Coastguard Worker
351*b7c941bbSAndroid Build Coastguard WorkerP = argparse.ArgumentParser("gar_v2")
352*b7c941bbSAndroid Build Coastguard WorkerP.add_argument("--dbg", "-v", action='store_true', help="dump debug info into xml")
353*b7c941bbSAndroid Build Coastguard WorkerP.add_argument("--ignore", "-I", action='store_true', help="ignore minimum sample count")
354*b7c941bbSAndroid Build Coastguard WorkerP.add_argument("result_zip", nargs="*")
355*b7c941bbSAndroid Build Coastguard WorkerA = P.parse_args()
356*b7c941bbSAndroid Build Coastguard Worker
357*b7c941bbSAndroid Build Coastguard WorkerD = Data()
358*b7c941bbSAndroid Build Coastguard Workerfor res in A.result_zip:
359*b7c941bbSAndroid Build Coastguard Worker  D.parse_result(res)
360*b7c941bbSAndroid Build Coastguard WorkerD.summarize(A=A)
361