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