Home | History | Annotate | Download | only in tko
      1 import os
      2 
      3 from autotest_lib.client.common_lib import utils
      4 from autotest_lib.tko import utils as tko_utils
      5 
      6 
      7 class 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 
     65 class 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 
     89 class 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 
    265 class 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 
    274 class 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 
    329 class 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