xref: /aosp_15_r20/external/autotest/tko/models.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Lifrom __future__ import division
3*9c5db199SXin Lifrom __future__ import print_function
4*9c5db199SXin Li
5*9c5db199SXin Liimport json
6*9c5db199SXin Liimport os
7*9c5db199SXin Li
8*9c5db199SXin Lifrom autotest_lib.server.hosts import file_store
9*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils
10*9c5db199SXin Lifrom autotest_lib.tko import tast
11*9c5db199SXin Lifrom autotest_lib.tko import utils as tko_utils
12*9c5db199SXin Liimport six
13*9c5db199SXin Li
14*9c5db199SXin Li
15*9c5db199SXin Liclass HostKeyvalError(Exception):
16*9c5db199SXin Li    """Raised when the host keyval cannot be read."""
17*9c5db199SXin Li
18*9c5db199SXin Li
19*9c5db199SXin Liclass job(object):
20*9c5db199SXin Li    """Represents a job."""
21*9c5db199SXin Li
22*9c5db199SXin Li    def __init__(self, dir, user, label, machine, queued_time, started_time,
23*9c5db199SXin Li                 finished_time, machine_owner, machine_group, aborted_by,
24*9c5db199SXin Li                 aborted_on, keyval_dict):
25*9c5db199SXin Li        self.dir = dir
26*9c5db199SXin Li        self.tests = []
27*9c5db199SXin Li        self.user = user
28*9c5db199SXin Li        self.label = label
29*9c5db199SXin Li        self.machine = machine
30*9c5db199SXin Li        self.queued_time = queued_time
31*9c5db199SXin Li        self.started_time = started_time
32*9c5db199SXin Li        self.finished_time = finished_time
33*9c5db199SXin Li        self.machine_owner = machine_owner
34*9c5db199SXin Li        self.machine_group = machine_group
35*9c5db199SXin Li        self.aborted_by = aborted_by
36*9c5db199SXin Li        self.aborted_on = aborted_on
37*9c5db199SXin Li        self.keyval_dict = keyval_dict
38*9c5db199SXin Li        self.afe_parent_job_id = None
39*9c5db199SXin Li        self.build_version = None
40*9c5db199SXin Li        self.suite = None
41*9c5db199SXin Li        self.board = None
42*9c5db199SXin Li        self.job_idx = None
43*9c5db199SXin Li        # id of the corresponding tko_task_references entry.
44*9c5db199SXin Li        # This table is used to refer to skylab task / afe job corresponding to
45*9c5db199SXin Li        # this tko_job.
46*9c5db199SXin Li        self.task_reference_id = None
47*9c5db199SXin Li
48*9c5db199SXin Li    @staticmethod
49*9c5db199SXin Li    def read_keyval(dir):
50*9c5db199SXin Li        """
51*9c5db199SXin Li        Read job keyval files.
52*9c5db199SXin Li
53*9c5db199SXin Li        @param dir: String name of directory containing job keyval files.
54*9c5db199SXin Li
55*9c5db199SXin Li        @return A dictionary containing job keyvals.
56*9c5db199SXin Li
57*9c5db199SXin Li        """
58*9c5db199SXin Li        dir = os.path.normpath(dir)
59*9c5db199SXin Li        top_dir = tko_utils.find_toplevel_job_dir(dir)
60*9c5db199SXin Li        if not top_dir:
61*9c5db199SXin Li            top_dir = dir
62*9c5db199SXin Li        assert(dir.startswith(top_dir))
63*9c5db199SXin Li
64*9c5db199SXin Li        # Pull in and merge all the keyval files, with higher-level
65*9c5db199SXin Li        # overriding values in the lower-level ones.
66*9c5db199SXin Li        keyval = {}
67*9c5db199SXin Li        while True:
68*9c5db199SXin Li            try:
69*9c5db199SXin Li                upper_keyval = utils.read_keyval(dir)
70*9c5db199SXin Li                # HACK: exclude hostname from the override - this is a special
71*9c5db199SXin Li                # case where we want lower to override higher.
72*9c5db199SXin Li                if 'hostname' in upper_keyval and 'hostname' in keyval:
73*9c5db199SXin Li                    del upper_keyval['hostname']
74*9c5db199SXin Li                keyval.update(upper_keyval)
75*9c5db199SXin Li            except IOError:
76*9c5db199SXin Li                pass  # If the keyval can't be read just move on to the next.
77*9c5db199SXin Li            if dir == top_dir:
78*9c5db199SXin Li                break
79*9c5db199SXin Li            else:
80*9c5db199SXin Li                assert(dir != '/')
81*9c5db199SXin Li                dir = os.path.dirname(dir)
82*9c5db199SXin Li        return keyval
83*9c5db199SXin Li
84*9c5db199SXin Li
85*9c5db199SXin Liclass kernel(object):
86*9c5db199SXin Li    """Represents a kernel."""
87*9c5db199SXin Li
88*9c5db199SXin Li    def __init__(self, base, patches, kernel_hash):
89*9c5db199SXin Li        self.base = base
90*9c5db199SXin Li        self.patches = patches
91*9c5db199SXin Li        self.kernel_hash = kernel_hash
92*9c5db199SXin Li
93*9c5db199SXin Li
94*9c5db199SXin Li    @staticmethod
95*9c5db199SXin Li    def compute_hash(base, hashes):
96*9c5db199SXin Li        """Compute a hash given the base string and hashes for each patch.
97*9c5db199SXin Li
98*9c5db199SXin Li        @param base: A string representing the kernel base.
99*9c5db199SXin Li        @param hashes: A list of hashes, where each hash is associated with a
100*9c5db199SXin Li            patch of this kernel.
101*9c5db199SXin Li
102*9c5db199SXin Li        @return A string representing the computed hash.
103*9c5db199SXin Li
104*9c5db199SXin Li        """
105*9c5db199SXin Li        key_string = ','.join([base] + hashes)
106*9c5db199SXin Li        return utils.hash('md5', key_string).hexdigest()
107*9c5db199SXin Li
108*9c5db199SXin Li
109*9c5db199SXin Liclass test(object):
110*9c5db199SXin Li    """Represents a test."""
111*9c5db199SXin Li
112*9c5db199SXin Li    def __init__(self, subdir, testname, status, reason, test_kernel,
113*9c5db199SXin Li                 machine, started_time, finished_time, iterations,
114*9c5db199SXin Li                 attributes, perf_values, labels):
115*9c5db199SXin Li        self.subdir = subdir
116*9c5db199SXin Li        self.testname = testname
117*9c5db199SXin Li        self.status = status
118*9c5db199SXin Li        self.reason = reason
119*9c5db199SXin Li        self.kernel = test_kernel
120*9c5db199SXin Li        self.machine = machine
121*9c5db199SXin Li        self.started_time = started_time
122*9c5db199SXin Li        self.finished_time = finished_time
123*9c5db199SXin Li        self.iterations = iterations
124*9c5db199SXin Li        self.attributes = attributes
125*9c5db199SXin Li        self.perf_values = perf_values
126*9c5db199SXin Li        self.labels = labels
127*9c5db199SXin Li
128*9c5db199SXin Li
129*9c5db199SXin Li    @staticmethod
130*9c5db199SXin Li    def load_iterations(keyval_path):
131*9c5db199SXin Li        """Abstract method to load a list of iterations from a keyval file.
132*9c5db199SXin Li
133*9c5db199SXin Li        @param keyval_path: String path to a keyval file.
134*9c5db199SXin Li
135*9c5db199SXin Li        @return A list of iteration objects.
136*9c5db199SXin Li
137*9c5db199SXin Li        """
138*9c5db199SXin Li        raise NotImplementedError
139*9c5db199SXin Li
140*9c5db199SXin Li
141*9c5db199SXin Li    @classmethod
142*9c5db199SXin Li    def parse_test(cls, job, subdir, testname, status, reason, test_kernel,
143*9c5db199SXin Li                   started_time, finished_time, existing_instance=None):
144*9c5db199SXin Li        """
145*9c5db199SXin Li        Parse test result files to construct a complete test instance.
146*9c5db199SXin Li
147*9c5db199SXin Li        Given a job and the basic metadata about the test that can be
148*9c5db199SXin Li        extracted from the status logs, parse the test result files (keyval
149*9c5db199SXin Li        files and perf measurement files) and use them to construct a complete
150*9c5db199SXin Li        test instance.
151*9c5db199SXin Li
152*9c5db199SXin Li        @param job: A job object.
153*9c5db199SXin Li        @param subdir: The string subdirectory name for the given test.
154*9c5db199SXin Li        @param testname: The name of the test.
155*9c5db199SXin Li        @param status: The status of the test.
156*9c5db199SXin Li        @param reason: The reason string for the test.
157*9c5db199SXin Li        @param test_kernel: The kernel of the test.
158*9c5db199SXin Li        @param started_time: The start time of the test.
159*9c5db199SXin Li        @param finished_time: The finish time of the test.
160*9c5db199SXin Li        @param existing_instance: An existing test instance.
161*9c5db199SXin Li
162*9c5db199SXin Li        @return A test instance that has the complete information.
163*9c5db199SXin Li
164*9c5db199SXin Li        """
165*9c5db199SXin Li        tko_utils.dprint("parsing test %s %s" % (subdir, testname))
166*9c5db199SXin Li
167*9c5db199SXin Li        if tast.is_tast_test(testname):
168*9c5db199SXin Li            attributes, perf_values = tast.load_tast_test_aux_results(job,
169*9c5db199SXin Li                                                                      testname)
170*9c5db199SXin Li            iterations = []
171*9c5db199SXin Li        elif subdir:
172*9c5db199SXin Li            # Grab iterations from the results keyval.
173*9c5db199SXin Li            iteration_keyval = os.path.join(job.dir, subdir,
174*9c5db199SXin Li                                            'results', 'keyval')
175*9c5db199SXin Li            iterations = cls.load_iterations(iteration_keyval)
176*9c5db199SXin Li
177*9c5db199SXin Li            # Grab perf values from the perf measurements file.
178*9c5db199SXin Li            perf_values_file = os.path.join(job.dir, subdir,
179*9c5db199SXin Li                                            'results', 'results-chart.json')
180*9c5db199SXin Li            perf_values = {}
181*9c5db199SXin Li            if os.path.exists(perf_values_file):
182*9c5db199SXin Li                with open(perf_values_file, 'r') as fp:
183*9c5db199SXin Li                    contents = fp.read()
184*9c5db199SXin Li                if contents:
185*9c5db199SXin Li                    perf_values = json.loads(contents)
186*9c5db199SXin Li
187*9c5db199SXin Li            # Grab test attributes from the subdir keyval.
188*9c5db199SXin Li            test_keyval = os.path.join(job.dir, subdir, 'keyval')
189*9c5db199SXin Li            attributes = test.load_attributes(test_keyval)
190*9c5db199SXin Li        else:
191*9c5db199SXin Li            iterations = []
192*9c5db199SXin Li            perf_values = {}
193*9c5db199SXin Li            attributes = {}
194*9c5db199SXin Li
195*9c5db199SXin Li        # Grab test+host attributes from the host keyval.
196*9c5db199SXin Li        host_keyval = cls.parse_host_keyval(job.dir, job.machine)
197*9c5db199SXin Li        attributes.update(dict(('host-%s' % k, v)
198*9c5db199SXin Li                               for k, v in six.iteritems(host_keyval)))
199*9c5db199SXin Li
200*9c5db199SXin Li        if existing_instance:
201*9c5db199SXin Li            def constructor(*args, **dargs):
202*9c5db199SXin Li                """Initializes an existing test instance."""
203*9c5db199SXin Li                existing_instance.__init__(*args, **dargs)
204*9c5db199SXin Li                return existing_instance
205*9c5db199SXin Li        else:
206*9c5db199SXin Li            constructor = cls
207*9c5db199SXin Li
208*9c5db199SXin Li        return constructor(subdir, testname, status, reason, test_kernel,
209*9c5db199SXin Li                           job.machine, started_time, finished_time,
210*9c5db199SXin Li                           iterations, attributes, perf_values, [])
211*9c5db199SXin Li
212*9c5db199SXin Li
213*9c5db199SXin Li    @classmethod
214*9c5db199SXin Li    def parse_partial_test(cls, job, subdir, testname, reason, test_kernel,
215*9c5db199SXin Li                           started_time):
216*9c5db199SXin Li        """
217*9c5db199SXin Li        Create a test instance representing a partial test result.
218*9c5db199SXin Li
219*9c5db199SXin Li        Given a job and the basic metadata available when a test is
220*9c5db199SXin Li        started, create a test instance representing the partial result.
221*9c5db199SXin Li        Assume that since the test is not complete there are no results files
222*9c5db199SXin Li        actually available for parsing.
223*9c5db199SXin Li
224*9c5db199SXin Li        @param job: A job object.
225*9c5db199SXin Li        @param subdir: The string subdirectory name for the given test.
226*9c5db199SXin Li        @param testname: The name of the test.
227*9c5db199SXin Li        @param reason: The reason string for the test.
228*9c5db199SXin Li        @param test_kernel: The kernel of the test.
229*9c5db199SXin Li        @param started_time: The start time of the test.
230*9c5db199SXin Li
231*9c5db199SXin Li        @return A test instance that has partial test information.
232*9c5db199SXin Li
233*9c5db199SXin Li        """
234*9c5db199SXin Li        tko_utils.dprint('parsing partial test %s %s' % (subdir, testname))
235*9c5db199SXin Li
236*9c5db199SXin Li        return cls(subdir, testname, 'RUNNING', reason, test_kernel,
237*9c5db199SXin Li                   job.machine, started_time, None, [], {}, [], [])
238*9c5db199SXin Li
239*9c5db199SXin Li
240*9c5db199SXin Li    @staticmethod
241*9c5db199SXin Li    def load_attributes(keyval_path):
242*9c5db199SXin Li        """
243*9c5db199SXin Li        Load test attributes from a test keyval path.
244*9c5db199SXin Li
245*9c5db199SXin Li        Load the test attributes into a dictionary from a test
246*9c5db199SXin Li        keyval path. Does not assume that the path actually exists.
247*9c5db199SXin Li
248*9c5db199SXin Li        @param keyval_path: The string path to a keyval file.
249*9c5db199SXin Li
250*9c5db199SXin Li        @return A dictionary representing the test keyvals.
251*9c5db199SXin Li
252*9c5db199SXin Li        """
253*9c5db199SXin Li        if not os.path.exists(keyval_path):
254*9c5db199SXin Li            return {}
255*9c5db199SXin Li        return utils.read_keyval(keyval_path)
256*9c5db199SXin Li
257*9c5db199SXin Li
258*9c5db199SXin Li    @staticmethod
259*9c5db199SXin Li    def _parse_keyval(job_dir, sub_keyval_path):
260*9c5db199SXin Li        """
261*9c5db199SXin Li        Parse a file of keyvals.
262*9c5db199SXin Li
263*9c5db199SXin Li        @param job_dir: The string directory name of the associated job.
264*9c5db199SXin Li        @param sub_keyval_path: Path to a keyval file relative to job_dir.
265*9c5db199SXin Li
266*9c5db199SXin Li        @return A dictionary representing the keyvals.
267*9c5db199SXin Li
268*9c5db199SXin Li        """
269*9c5db199SXin Li        # The "real" job dir may be higher up in the directory tree.
270*9c5db199SXin Li        job_dir = tko_utils.find_toplevel_job_dir(job_dir)
271*9c5db199SXin Li        if not job_dir:
272*9c5db199SXin Li            return {}  # We can't find a top-level job dir with job keyvals.
273*9c5db199SXin Li
274*9c5db199SXin Li        # The keyval is <job_dir>/`sub_keyval_path` if it exists.
275*9c5db199SXin Li        keyval_path = os.path.join(job_dir, sub_keyval_path)
276*9c5db199SXin Li        if os.path.isfile(keyval_path):
277*9c5db199SXin Li            return utils.read_keyval(keyval_path)
278*9c5db199SXin Li        else:
279*9c5db199SXin Li            return {}
280*9c5db199SXin Li
281*9c5db199SXin Li
282*9c5db199SXin Li    @staticmethod
283*9c5db199SXin Li    def _is_multimachine(job_dir):
284*9c5db199SXin Li        """
285*9c5db199SXin Li        Determine whether the job is a multi-machine job.
286*9c5db199SXin Li
287*9c5db199SXin Li        @param job_dir: The string directory name of the associated job.
288*9c5db199SXin Li
289*9c5db199SXin Li        @return True, if the job is a multi-machine job, or False if not.
290*9c5db199SXin Li
291*9c5db199SXin Li        """
292*9c5db199SXin Li        machines_path = os.path.join(job_dir, '.machines')
293*9c5db199SXin Li        if os.path.exists(machines_path):
294*9c5db199SXin Li            with open(machines_path, 'r') as fp:
295*9c5db199SXin Li                line_count = len(fp.read().splitlines())
296*9c5db199SXin Li                if line_count > 1:
297*9c5db199SXin Li                    return True
298*9c5db199SXin Li        return False
299*9c5db199SXin Li
300*9c5db199SXin Li
301*9c5db199SXin Li    @staticmethod
302*9c5db199SXin Li    def parse_host_keyval(job_dir, hostname):
303*9c5db199SXin Li        """
304*9c5db199SXin Li        Parse host keyvals.
305*9c5db199SXin Li
306*9c5db199SXin Li        @param job_dir: The string directory name of the associated job.
307*9c5db199SXin Li        @param hostname: The string hostname.
308*9c5db199SXin Li
309*9c5db199SXin Li        @return A dictionary representing the host keyvals.
310*9c5db199SXin Li
311*9c5db199SXin Li        @raises HostKeyvalError if the host keyval is not found.
312*9c5db199SXin Li
313*9c5db199SXin Li        """
314*9c5db199SXin Li        keyval_path = os.path.join('host_keyvals', hostname)
315*9c5db199SXin Li        hostinfo_path = os.path.join(job_dir, 'host_info_store',
316*9c5db199SXin Li                                     hostname + '.store')
317*9c5db199SXin Li        # Skylab uses hostinfo. If this is not present, try falling back to the
318*9c5db199SXin Li        # host keyval file (moblab), or an empty host keyval for multi-machine
319*9c5db199SXin Li        # tests (jetstream).
320*9c5db199SXin Li        if os.path.exists(hostinfo_path):
321*9c5db199SXin Li            tko_utils.dprint('Reading keyvals from hostinfo.')
322*9c5db199SXin Li            return _parse_hostinfo_keyval(hostinfo_path)
323*9c5db199SXin Li        elif os.path.exists(os.path.join(job_dir, keyval_path)):
324*9c5db199SXin Li            tko_utils.dprint('Reading keyvals from %s.' % keyval_path)
325*9c5db199SXin Li            return test._parse_keyval(job_dir, keyval_path)
326*9c5db199SXin Li        elif test._is_multimachine(job_dir):
327*9c5db199SXin Li            tko_utils.dprint('Multimachine job, no keyvals.')
328*9c5db199SXin Li            return {}
329*9c5db199SXin Li        raise HostKeyvalError('Host keyval not found')
330*9c5db199SXin Li
331*9c5db199SXin Li
332*9c5db199SXin Li    @staticmethod
333*9c5db199SXin Li    def parse_job_keyval(job_dir):
334*9c5db199SXin Li        """
335*9c5db199SXin Li        Parse job keyvals.
336*9c5db199SXin Li
337*9c5db199SXin Li        @param job_dir: The string directory name of the associated job.
338*9c5db199SXin Li
339*9c5db199SXin Li        @return A dictionary representing the job keyvals.
340*9c5db199SXin Li
341*9c5db199SXin Li        """
342*9c5db199SXin Li        # The job keyval is <job_dir>/keyval if it exists.
343*9c5db199SXin Li        return test._parse_keyval(job_dir, 'keyval')
344*9c5db199SXin Li
345*9c5db199SXin Li
346*9c5db199SXin Lidef _parse_hostinfo_keyval(hostinfo_path):
347*9c5db199SXin Li    """
348*9c5db199SXin Li    Parse host keyvals from hostinfo.
349*9c5db199SXin Li
350*9c5db199SXin Li    @param hostinfo_path: The string path to the host info store file.
351*9c5db199SXin Li
352*9c5db199SXin Li    @return A dictionary representing the host keyvals.
353*9c5db199SXin Li
354*9c5db199SXin Li    """
355*9c5db199SXin Li    store = file_store.FileStore(hostinfo_path)
356*9c5db199SXin Li    hostinfo = store.get()
357*9c5db199SXin Li    # TODO(ayatane): Investigate if urllib.quote is better.
358*9c5db199SXin Li    label_string = ','.join(label.replace(':', '%3A')
359*9c5db199SXin Li                            for label in hostinfo.labels)
360*9c5db199SXin Li    return {
361*9c5db199SXin Li            'labels': label_string,
362*9c5db199SXin Li            'platform': hostinfo.model,
363*9c5db199SXin Li            'board': hostinfo.board
364*9c5db199SXin Li    }
365*9c5db199SXin Li
366*9c5db199SXin Li
367*9c5db199SXin Liclass patch(object):
368*9c5db199SXin Li    """Represents a patch."""
369*9c5db199SXin Li
370*9c5db199SXin Li    def __init__(self, spec, reference, hash):
371*9c5db199SXin Li        self.spec = spec
372*9c5db199SXin Li        self.reference = reference
373*9c5db199SXin Li        self.hash = hash
374*9c5db199SXin Li
375*9c5db199SXin Li
376*9c5db199SXin Liclass iteration(object):
377*9c5db199SXin Li    """Represents an iteration."""
378*9c5db199SXin Li
379*9c5db199SXin Li    def __init__(self, index, attr_keyval, perf_keyval):
380*9c5db199SXin Li        self.index = index
381*9c5db199SXin Li        self.attr_keyval = attr_keyval
382*9c5db199SXin Li        self.perf_keyval = perf_keyval
383*9c5db199SXin Li
384*9c5db199SXin Li
385*9c5db199SXin Li    @staticmethod
386*9c5db199SXin Li    def parse_line_into_dicts(line, attr_dict, perf_dict):
387*9c5db199SXin Li        """
388*9c5db199SXin Li        Abstract method to parse a keyval line and insert it into a dictionary.
389*9c5db199SXin Li
390*9c5db199SXin Li        @param line: The string line to parse.
391*9c5db199SXin Li        @param attr_dict: Dictionary of generic iteration attributes.
392*9c5db199SXin Li        @param perf_dict: Dictionary of iteration performance results.
393*9c5db199SXin Li
394*9c5db199SXin Li        """
395*9c5db199SXin Li        raise NotImplementedError
396*9c5db199SXin Li
397*9c5db199SXin Li
398*9c5db199SXin Li    @classmethod
399*9c5db199SXin Li    def load_from_keyval(cls, keyval_path):
400*9c5db199SXin Li        """
401*9c5db199SXin Li        Load a list of iterations from an iteration keyval file.
402*9c5db199SXin Li
403*9c5db199SXin Li        Keyval data from separate iterations is separated by blank
404*9c5db199SXin Li        lines. Makes use of the parse_line_into_dicts method to
405*9c5db199SXin Li        actually parse the individual lines.
406*9c5db199SXin Li
407*9c5db199SXin Li        @param keyval_path: The string path to a keyval file.
408*9c5db199SXin Li
409*9c5db199SXin Li        @return A list of iteration objects.
410*9c5db199SXin Li
411*9c5db199SXin Li        """
412*9c5db199SXin Li        if not os.path.exists(keyval_path):
413*9c5db199SXin Li            return []
414*9c5db199SXin Li
415*9c5db199SXin Li        iterations = []
416*9c5db199SXin Li        index = 1
417*9c5db199SXin Li        attr, perf = {}, {}
418*9c5db199SXin Li        with open(keyval_path, 'r') as kp:
419*9c5db199SXin Li            lines = kp.readlines()
420*9c5db199SXin Li        for line in lines:
421*9c5db199SXin Li            line = line.strip()
422*9c5db199SXin Li            if line:
423*9c5db199SXin Li                cls.parse_line_into_dicts(line, attr, perf)
424*9c5db199SXin Li            else:
425*9c5db199SXin Li                iterations.append(cls(index, attr, perf))
426*9c5db199SXin Li                index += 1
427*9c5db199SXin Li                attr, perf = {}, {}
428*9c5db199SXin Li        if attr or perf:
429*9c5db199SXin Li            iterations.append(cls(index, attr, perf))
430*9c5db199SXin Li        return iterations
431*9c5db199SXin Li
432*9c5db199SXin Li
433*9c5db199SXin Liclass perf_value_iteration(object):
434*9c5db199SXin Li    """Represents a perf value iteration."""
435*9c5db199SXin Li
436*9c5db199SXin Li    def __init__(self, index, perf_measurements):
437*9c5db199SXin Li        """
438*9c5db199SXin Li        Initializes the perf values for a particular test iteration.
439*9c5db199SXin Li
440*9c5db199SXin Li        @param index: The integer iteration number.
441*9c5db199SXin Li        @param perf_measurements: A list of dictionaries, where each dictionary
442*9c5db199SXin Li            contains the information for a measured perf metric from the
443*9c5db199SXin Li            current iteration.
444*9c5db199SXin Li
445*9c5db199SXin Li        """
446*9c5db199SXin Li        self.index = index
447*9c5db199SXin Li        self.perf_measurements = perf_measurements
448*9c5db199SXin Li
449*9c5db199SXin Li
450*9c5db199SXin Li    @staticmethod
451*9c5db199SXin Li    def parse_line_into_dict(line):
452*9c5db199SXin Li        """
453*9c5db199SXin Li        Abstract method to parse an individual perf measurement line.
454*9c5db199SXin Li
455*9c5db199SXin Li        @param line: A string line from the perf measurement output file.
456*9c5db199SXin Li
457*9c5db199SXin Li        @return A dicionary representing the information for a measured perf
458*9c5db199SXin Li            metric from one line of the perf measurement output file, or an
459*9c5db199SXin Li            empty dictionary if the line cannot be parsed successfully.
460*9c5db199SXin Li
461*9c5db199SXin Li        """
462*9c5db199SXin Li        raise NotImplementedError
463