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