Home | History | Annotate | Download | only in tko
      1 import json
      2 import os
      3 
      4 from autotest_lib.client.common_lib import utils
      5 from autotest_lib.tko import utils as tko_utils
      6 
      7 
      8 class 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 
     70 class 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 
     94 class 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 
    305 class 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 
    314 class 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 
    369 class 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