Home | History | Annotate | Download | only in cros
      1 # Copyright 2015 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license
      3 # that can be found in the LICENSE file.
      4 
      5 import argparse
      6 import logging
      7 import mmap
      8 import os
      9 import signal
     10 import struct
     11 import sys
     12 import threading
     13 import time
     14 
     15 # some magic numbers: see http://goo.gl/ecAgke for Intel docs
     16 PCI_IMC_BAR_OFFSET = 0x48
     17 IMC_DRAM_GT_REQUESTS = 0x5040 # GPU
     18 IMC_DRAM_IA_REQUESTS = 0x5044 # CPU
     19 IMC_DRAM_IO_REQUESTS = 0x5048 # PCIe, Display Engine, USB, etc.
     20 IMC_DRAM_DATA_READS = 0x5050  # read traffic
     21 IMC_DRAM_DATA_WRITES = 0x5054 # write traffic
     22 IMC_MMAP_SIZE = 0x6000
     23 
     24 CACHE_LINE = 64.0
     25 MEGABYTE = 1048576.0
     26 
     27 RATE_FIELD_FORMAT = '%s: %5d MB/s'
     28 RAW_FIELD_FORMAT = '%s: %d'
     29 
     30 class IMCCounter:
     31     """Small struct-like class to keep track of the
     32     location and attributes for each counter.
     33 
     34     Parameters:
     35       name: short, unique identifying token for this
     36         counter type
     37       idx: offset into the IMC memory where we can find
     38         this counter
     39       total: True if we should count this in the number
     40         for total bandwidth
     41     """
     42     def __init__(self, name, idx, total):
     43         self.name = name
     44         self.idx = idx
     45         self.total = total
     46 
     47 
     48 counters = [
     49 #              name          idx           total
     50     IMCCounter("GT", IMC_DRAM_GT_REQUESTS, False),
     51     IMCCounter("IA", IMC_DRAM_IA_REQUESTS, False),
     52     IMCCounter("IO", IMC_DRAM_IO_REQUESTS, False),
     53     IMCCounter("RD", IMC_DRAM_DATA_READS,  True),
     54     IMCCounter("WR", IMC_DRAM_DATA_WRITES, True),
     55 ]
     56 
     57 
     58 class MappedFile:
     59     """Helper class to wrap mmap calls in a context
     60     manager so they are always cleaned up, and to
     61     help extract values from the bytes.
     62 
     63     Parameters:
     64       filename: name of file to mmap
     65       offset: offset from beginning of file to mmap
     66         from
     67       size: amount of the file to mmap
     68     """
     69     def __init__(self, filename, offset, size):
     70         self._filename = filename
     71         self._offset = offset
     72         self._size = size
     73 
     74 
     75     def __enter__(self):
     76         self._f = open(self._filename, 'rb')
     77         try:
     78             self._mm = mmap.mmap(self._f.fileno(),
     79                                  self._size,
     80                                  mmap.MAP_SHARED,
     81                                  mmap.PROT_READ,
     82                                  offset=self._offset)
     83         except mmap.error:
     84             self._f.close()
     85             raise
     86         return self
     87 
     88 
     89     def __exit__(self, exc_type, exc_val, exc_tb):
     90         self._mm.close()
     91         self._f.close()
     92 
     93 
     94     def bytes_to_python(self, offset, fmt):
     95         """Grab a portion of an mmapped file and return the bytes
     96         as a python object.
     97 
     98         Parameters:
     99           offset: offset into the mmapped file to start at
    100           fmt: string containing the struct type to extract from the
    101             file
    102         Returns: a Struct containing the bytes starting at offset
    103           into the mmapped file, reified as python values
    104         """
    105         s = struct.Struct(fmt)
    106         return s.unpack(self._mm[offset:offset+s.size])
    107 
    108 
    109 def file_bytes_to_python(f, offset, fmt):
    110     """Grab a portion of a regular file and return the bytes
    111     as a python object.
    112 
    113     Parameters:
    114       f: file-like object to extract from
    115       offset: offset into the mmapped file to start at
    116       fmt: string containing the struct type to extract from the
    117         file
    118     Returns: a Struct containing the bytes starting at offset into
    119       f, reified as python values
    120     """
    121     s = struct.Struct(fmt)
    122     f.seek(0)
    123     bs = f.read()
    124     if len(bs) >= offset + s.size:
    125         return s.unpack(bs[offset:offset+s.size])
    126     else:
    127         raise IOError('Invalid seek in file')
    128 
    129 
    130 def uint32_diff(l, r):
    131     """Compute the difference of two 32-bit numbers as
    132     another 32-bit number.
    133 
    134     Since the counters are monotonically increasing, we
    135     always want the unsigned difference.
    136     """
    137     return l - r if l >= r else l - r + 0x100000000
    138 
    139 
    140 class MemoryBandwidthLogger(threading.Thread):
    141     """Class for gathering memory usage in MB/s on x86 systems.
    142     raw: dump raw counter values
    143     seconds_period: time period between reads
    144 
    145     If you are using non-raw mode and your seconds_period is
    146     too high, your results might be nonsense because the counters
    147     might have wrapped around.
    148 
    149     Parameters:
    150       raw: True if you want to dump raw counters. These will simply
    151         tell you the number of cache-line-size transactions that
    152         have occurred so far.
    153       seconds_period: Duration to wait before dumping counters again.
    154         Defaults to 2 seconds.
    155       """
    156     def __init__(self, raw, seconds_period=2):
    157         super(MemoryBandwidthLogger, self).__init__()
    158         self._raw = raw
    159         self._seconds_period = seconds_period
    160         self._running = True
    161 
    162 
    163     def run(self):
    164         # get base address register and align to 4k
    165         try:
    166             bar_addr = self._get_pci_imc_bar()
    167         except IOError:
    168             logging.error('Cannot read base address register')
    169             return
    170         bar_addr = (bar_addr // 4096) * 4096
    171 
    172         # set up the output formatting. raw counters don't have any
    173         # particular meaning in MB/s since they count how many cache
    174         # lines have been read from or written to up to that point,
    175         # and so don't represent a rate.
    176         # TOTAL is always given as a rate, though.
    177         rate_factor = CACHE_LINE / (self._seconds_period * MEGABYTE)
    178         if self._raw:
    179             field_format = RAW_FIELD_FORMAT
    180         else:
    181             field_format = RATE_FIELD_FORMAT
    182 
    183         # get /dev/mem and mmap it
    184         with MappedFile('/dev/mem', bar_addr, IMC_MMAP_SIZE) as mm:
    185             # take initial samples, then take samples every seconds_period
    186             last_values = self._take_samples(mm)
    187             while self._running:
    188                 time.sleep(self._seconds_period)
    189                 values = self._take_samples(mm)
    190                 # we need to calculate the MB differences no matter what
    191                 # because the "total" field uses it even when we are in
    192                 # raw mode
    193                 mb_diff = { c.name:
    194                     uint32_diff(values[c.name], last_values[c.name])
    195                         * rate_factor for c in counters }
    196                 output_dict = values if self._raw else mb_diff
    197                 output = list((c.name, output_dict[c.name]) for c in counters)
    198 
    199                 total_rate = sum(mb_diff[c.name] for c in counters if c.total)
    200                 output_str = \
    201                     ' '.join(field_format % (k, v) for k, v in output) + \
    202                     ' ' + (RATE_FIELD_FORMAT % ('TOTAL', total_rate))
    203 
    204                 logging.debug(output_str)
    205                 last_values = values
    206 
    207 
    208     def stop(self):
    209         self._running = False
    210 
    211 
    212     def _get_pci_imc_bar(self):
    213         """Get the base address register for the IMC (integrated
    214         memory controller). This is later used to extract counter
    215         values.
    216 
    217         Returns: physical address for the IMC.
    218         """
    219         with open('/proc/bus/pci/00/00.0', 'rb') as pci:
    220             return file_bytes_to_python(pci, PCI_IMC_BAR_OFFSET, '=Q')[0]
    221 
    222 
    223     def _take_samples(self, mm):
    224         """Get samples for each type of memory transaction.
    225 
    226         Parameters:
    227           mm: MappedFile representing physical memory
    228         Returns: dictionary mapping counter type to counter value
    229         """
    230         return { c.name: mm.bytes_to_python(c.idx, '=I')[0]
    231             for c in counters }
    232