Home | History | Annotate | Download | only in module
      1 #    Copyright 2017 ARM Limited
      2 #
      3 # Licensed under the Apache License, Version 2.0 (the "License");
      4 # you may not use this file except in compliance with the License.
      5 # You may obtain a copy of the License at
      6 #
      7 #     http://www.apache.org/licenses/LICENSE-2.0
      8 #
      9 # Unless required by applicable law or agreed to in writing, software
     10 # distributed under the License is distributed on an "AS IS" BASIS,
     11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 # See the License for the specific language governing permissions and
     13 # limitations under the License.
     14 
     15 import re
     16 import sys
     17 import logging
     18 import os.path
     19 from collections import defaultdict
     20 
     21 import devlib
     22 from devlib.exception import TargetError
     23 from devlib.module import Module
     24 from devlib.platform import Platform
     25 from devlib.platform.gem5 import Gem5SimulationPlatform
     26 from devlib.utils.gem5 import iter_statistics_dump, GEM5STATS_ROI_NUMBER, GEM5STATS_DUMP_TAIL
     27 
     28 
     29 class Gem5ROI:
     30     def __init__(self, number, target):
     31         self.target = target
     32         self.number = number
     33         self.running = False
     34         self.field = 'ROI::{}'.format(number)
     35 
     36     def start(self):
     37         if self.running:
     38             return False
     39         self.target.execute('m5 roistart {}'.format(self.number))
     40         self.running = True
     41         return True
     42     
     43     def stop(self):
     44         if not self.running:
     45             return False
     46         self.target.execute('m5 roiend {}'.format(self.number))
     47         self.running = False
     48         return True
     49 
     50 class Gem5StatsModule(Module):
     51     '''
     52     Module controlling Region of Interest (ROIs) markers, satistics dump 
     53     frequency and parsing statistics log file when using gem5 platforms.
     54 
     55     ROIs are identified by user-defined labels and need to be booked prior to
     56     use. The translation of labels into gem5 ROI numbers will be performed
     57     internally in order to avoid conflicts between multiple clients.
     58     '''
     59     name = 'gem5stats'
     60 
     61     @staticmethod
     62     def probe(target):
     63         return isinstance(target.platform, Gem5SimulationPlatform)
     64 
     65     def __init__(self, target):
     66         super(Gem5StatsModule, self).__init__(target)
     67         self._current_origin = 0
     68         self._stats_file_path = os.path.join(target.platform.gem5_out_dir,
     69                                             'stats.txt')
     70         self.rois = {}
     71         self._dump_pos_cache = {0: 0}
     72 
     73     def book_roi(self, label):
     74         if label in self.rois:
     75             raise KeyError('ROI label {} already used'.format(label))
     76         if len(self.rois) >= GEM5STATS_ROI_NUMBER:
     77             raise RuntimeError('Too many ROIs reserved')
     78         all_rois = set(xrange(GEM5STATS_ROI_NUMBER))
     79         used_rois = set([roi.number for roi in self.rois.values()])
     80         avail_rois = all_rois - used_rois
     81         self.rois[label] = Gem5ROI(list(avail_rois)[0], self.target)
     82 
     83     def free_roi(self, label):
     84         if label not in self.rois:
     85             raise KeyError('ROI label {} not reserved yet'.format(label))
     86         self.rois[label].stop()
     87         del self.rois[label]
     88 
     89     def roi_start(self, label):
     90         if label not in self.rois:
     91             raise KeyError('Incorrect ROI label: {}'.format(label))
     92         if not self.rois[label].start():
     93             raise TargetError('ROI {} was already running'.format(label))
     94     
     95     def roi_end(self, label):
     96         if label not in self.rois:
     97             raise KeyError('Incorrect ROI label: {}'.format(label))
     98         if not self.rois[label].stop():
     99             raise TargetError('ROI {} was not running'.format(label))
    100 
    101     def start_periodic_dump(self, delay_ns=0, period_ns=10000000):
    102         # Default period is 10ms because it's roughly what's needed to have
    103         # accurate power estimations
    104         if delay_ns < 0 or period_ns < 0:
    105             msg = 'Delay ({}) and period ({}) for periodic dumps must be positive'
    106             raise ValueError(msg.format(delay_ns, period_ns))
    107         self.target.execute('m5 dumpresetstats {} {}'.format(delay_ns, period_ns))
    108     
    109     def match(self, keys, rois_labels, base_dump=0):
    110         '''
    111         Extract specific values from the statistics log file of gem5
    112 
    113         :param keys: a list of key name or regular expression patterns that
    114             will be matched in the fields of the statistics file. ``match()``
    115             returns only the values of fields matching at least one these
    116             keys.
    117         :type keys: list
    118 
    119         :param rois_labels: list of ROIs labels. ``match()`` returns the 
    120             values of the specified fields only during dumps spanned by at
    121             least one of these ROIs.
    122         :type rois_label: list
    123 
    124         :param base_dump: dump number from which ``match()`` should operate. By 
    125             specifying a non-zero dump number, one can virtually truncate 
    126             the head of the stats file and ignore all dumps before a specific
    127             instant. The value of ``base_dump`` will typically (but not 
    128             necessarily) be the result of a previous call to ``next_dump_no``.
    129             Default value is 0.
    130         :type base_dump: int
    131 
    132         :returns: a dict indexed by key parameters containing a dict indexed by
    133         ROI labels containing an in-order list of records for the key under
    134         consideration during the active intervals of the ROI. 
    135         
    136         Example of return value:
    137          * Result of match(['sim_'],['roi_1']):
    138             {
    139                 'sim_inst': 
    140                 {
    141                     'roi_1': [265300176, 267975881]
    142                 }
    143                 'sim_ops': 
    144                 {
    145                     'roi_1': [324395787, 327699419]
    146                 }
    147                 'sim_seconds': 
    148                 {
    149                     'roi_1': [0.199960, 0.199897]
    150                 }
    151                 'sim_freq': 
    152                 {
    153                     'roi_1': [1000000000000, 1000000000000]
    154                 }
    155                 'sim_ticks': 
    156                 {
    157                     'roi_1': [199960234227, 199896897330]
    158                 }
    159             }
    160         '''
    161         records = defaultdict(lambda : defaultdict(list))
    162         for record, active_rois in self.match_iter(keys, rois_labels, base_dump):
    163             for key in record:
    164                 for roi_label in active_rois:
    165                     records[key][roi_label].append(record[key])
    166         return records
    167 
    168     def match_iter(self, keys, rois_labels, base_dump=0):
    169         '''
    170         Yield specific values dump-by-dump from the statistics log file of gem5
    171 
    172         :param keys: same as ``match()``
    173         :param rois_labels: same as ``match()``
    174         :param base_dump: same as ``match()``
    175         :returns: a pair containing:
    176             1. a dict storing the values corresponding to each of the found keys
    177             2. the list of currently active ROIs among those passed as parameters
    178 
    179         Example of return value:
    180          * Result of match_iter(['sim_'],['roi_1', 'roi_2']).next()
    181             ( 
    182                 { 
    183                     'sim_inst': 265300176,
    184                     'sim_ops': 324395787,
    185                     'sim_seconds': 0.199960, 
    186                     'sim_freq': 1000000000000,
    187                     'sim_ticks': 199960234227,
    188                 },
    189                 [ 'roi_1 ' ] 
    190             )
    191         '''
    192         for label in rois_labels:
    193             if label not in self.rois:
    194                 raise KeyError('Impossible to match ROI label {}'.format(label))
    195             if self.rois[label].running:
    196                 self.logger.warning('Trying to match records in statistics file'
    197                         ' while ROI {} is running'.format(label))
    198         
    199         # Construct one large regex that concatenates all keys because
    200         # matching one large expression is more efficient than several smaller
    201         all_keys_re = re.compile('|'.join(keys))
    202         
    203         def roi_active(roi_label, dump):
    204             roi = self.rois[roi_label]
    205             return (roi.field in dump) and (int(dump[roi.field]) == 1)
    206 
    207         with open(self._stats_file_path, 'r') as stats_file:
    208             self._goto_dump(stats_file, base_dump)
    209             for dump in iter_statistics_dump(stats_file):
    210                 active_rois = [l for l in rois_labels if roi_active(l, dump)]
    211                 if active_rois:
    212                     rec = {k: dump[k] for k in dump if all_keys_re.search(k)}
    213                     yield (rec, active_rois)
    214 
    215     def next_dump_no(self):
    216         '''
    217         Returns the number of the next dump to be written to the stats file.
    218         
    219         For example, if next_dump_no is called while there are 5 (0 to 4) full 
    220         dumps in the stats file, it will return 5. This will be usefull to know
    221         from which dump one should match() in the future to get only data from
    222         now on.
    223         '''
    224         with open(self._stats_file_path, 'r') as stats_file:
    225             # _goto_dump reach EOF and returns the total number of dumps + 1
    226             return self._goto_dump(stats_file, sys.maxint)
    227     
    228     def _goto_dump(self, stats_file, target_dump):
    229         if target_dump < 0:
    230             raise HostError('Cannot go to dump {}'.format(target_dump))
    231 
    232         # Go to required dump quickly if it was visited before
    233         if target_dump in self._dump_pos_cache:
    234             stats_file.seek(self._dump_pos_cache[target_dump])
    235             return target_dump
    236         # Or start from the closest dump already visited before the required one
    237         prev_dumps = filter(lambda x: x < target_dump, self._dump_pos_cache.keys())
    238         curr_dump = max(prev_dumps)
    239         curr_pos = self._dump_pos_cache[curr_dump]
    240         stats_file.seek(curr_pos)
    241         
    242         # And iterate until target_dump
    243         dump_iterator = iter_statistics_dump(stats_file)
    244         while curr_dump < target_dump:
    245             try:
    246                 dump = dump_iterator.next()
    247             except StopIteration:
    248                 break
    249             # End of passed dump is beginning og next one
    250             curr_pos = stats_file.tell()
    251             curr_dump += 1
    252         self._dump_pos_cache[curr_dump] = curr_pos
    253         return curr_dump
    254 
    255