普通文本  |  232行  |  7.34 KB

# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file.

import argparse
import logging
import mmap
import os
import signal
import struct
import sys
import threading
import time

# some magic numbers: see http://goo.gl/ecAgke for Intel docs
PCI_IMC_BAR_OFFSET = 0x48
IMC_DRAM_GT_REQUESTS = 0x5040 # GPU
IMC_DRAM_IA_REQUESTS = 0x5044 # CPU
IMC_DRAM_IO_REQUESTS = 0x5048 # PCIe, Display Engine, USB, etc.
IMC_DRAM_DATA_READS = 0x5050  # read traffic
IMC_DRAM_DATA_WRITES = 0x5054 # write traffic
IMC_MMAP_SIZE = 0x6000

CACHE_LINE = 64.0
MEGABYTE = 1048576.0

RATE_FIELD_FORMAT = '%s: %5d MB/s'
RAW_FIELD_FORMAT = '%s: %d'

class IMCCounter:
    """Small struct-like class to keep track of the
    location and attributes for each counter.

    Parameters:
      name: short, unique identifying token for this
        counter type
      idx: offset into the IMC memory where we can find
        this counter
      total: True if we should count this in the number
        for total bandwidth
    """
    def __init__(self, name, idx, total):
        self.name = name
        self.idx = idx
        self.total = total


counters = [
#              name          idx           total
    IMCCounter("GT", IMC_DRAM_GT_REQUESTS, False),
    IMCCounter("IA", IMC_DRAM_IA_REQUESTS, False),
    IMCCounter("IO", IMC_DRAM_IO_REQUESTS, False),
    IMCCounter("RD", IMC_DRAM_DATA_READS,  True),
    IMCCounter("WR", IMC_DRAM_DATA_WRITES, True),
]


class MappedFile:
    """Helper class to wrap mmap calls in a context
    manager so they are always cleaned up, and to
    help extract values from the bytes.

    Parameters:
      filename: name of file to mmap
      offset: offset from beginning of file to mmap
        from
      size: amount of the file to mmap
    """
    def __init__(self, filename, offset, size):
        self._filename = filename
        self._offset = offset
        self._size = size


    def __enter__(self):
        self._f = open(self._filename, 'rb')
        try:
            self._mm = mmap.mmap(self._f.fileno(),
                                 self._size,
                                 mmap.MAP_SHARED,
                                 mmap.PROT_READ,
                                 offset=self._offset)
        except mmap.error:
            self._f.close()
            raise
        return self


    def __exit__(self, exc_type, exc_val, exc_tb):
        self._mm.close()
        self._f.close()


    def bytes_to_python(self, offset, fmt):
        """Grab a portion of an mmapped file and return the bytes
        as a python object.

        Parameters:
          offset: offset into the mmapped file to start at
          fmt: string containing the struct type to extract from the
            file
        Returns: a Struct containing the bytes starting at offset
          into the mmapped file, reified as python values
        """
        s = struct.Struct(fmt)
        return s.unpack(self._mm[offset:offset+s.size])


def file_bytes_to_python(f, offset, fmt):
    """Grab a portion of a regular file and return the bytes
    as a python object.

    Parameters:
      f: file-like object to extract from
      offset: offset into the mmapped file to start at
      fmt: string containing the struct type to extract from the
        file
    Returns: a Struct containing the bytes starting at offset into
      f, reified as python values
    """
    s = struct.Struct(fmt)
    f.seek(0)
    bs = f.read()
    if len(bs) >= offset + s.size:
        return s.unpack(bs[offset:offset+s.size])
    else:
        raise IOError('Invalid seek in file')


def uint32_diff(l, r):
    """Compute the difference of two 32-bit numbers as
    another 32-bit number.

    Since the counters are monotonically increasing, we
    always want the unsigned difference.
    """
    return l - r if l >= r else l - r + 0x100000000


class MemoryBandwidthLogger(threading.Thread):
    """Class for gathering memory usage in MB/s on x86 systems.
    raw: dump raw counter values
    seconds_period: time period between reads

    If you are using non-raw mode and your seconds_period is
    too high, your results might be nonsense because the counters
    might have wrapped around.

    Parameters:
      raw: True if you want to dump raw counters. These will simply
        tell you the number of cache-line-size transactions that
        have occurred so far.
      seconds_period: Duration to wait before dumping counters again.
        Defaults to 2 seconds.
      """
    def __init__(self, raw, seconds_period=2):
        super(MemoryBandwidthLogger, self).__init__()
        self._raw = raw
        self._seconds_period = seconds_period
        self._running = True


    def run(self):
        # get base address register and align to 4k
        try:
            bar_addr = self._get_pci_imc_bar()
        except IOError:
            logging.error('Cannot read base address register')
            return
        bar_addr = (bar_addr // 4096) * 4096

        # set up the output formatting. raw counters don't have any
        # particular meaning in MB/s since they count how many cache
        # lines have been read from or written to up to that point,
        # and so don't represent a rate.
        # TOTAL is always given as a rate, though.
        rate_factor = CACHE_LINE / (self._seconds_period * MEGABYTE)
        if self._raw:
            field_format = RAW_FIELD_FORMAT
        else:
            field_format = RATE_FIELD_FORMAT

        # get /dev/mem and mmap it
        with MappedFile('/dev/mem', bar_addr, IMC_MMAP_SIZE) as mm:
            # take initial samples, then take samples every seconds_period
            last_values = self._take_samples(mm)
            while self._running:
                time.sleep(self._seconds_period)
                values = self._take_samples(mm)
                # we need to calculate the MB differences no matter what
                # because the "total" field uses it even when we are in
                # raw mode
                mb_diff = { c.name:
                    uint32_diff(values[c.name], last_values[c.name])
                        * rate_factor for c in counters }
                output_dict = values if self._raw else mb_diff
                output = list((c.name, output_dict[c.name]) for c in counters)

                total_rate = sum(mb_diff[c.name] for c in counters if c.total)
                output_str = \
                    ' '.join(field_format % (k, v) for k, v in output) + \
                    ' ' + (RATE_FIELD_FORMAT % ('TOTAL', total_rate))

                logging.debug(output_str)
                last_values = values


    def stop(self):
        self._running = False


    def _get_pci_imc_bar(self):
        """Get the base address register for the IMC (integrated
        memory controller). This is later used to extract counter
        values.

        Returns: physical address for the IMC.
        """
        with open('/proc/bus/pci/00/00.0', 'rb') as pci:
            return file_bytes_to_python(pci, PCI_IMC_BAR_OFFSET, '=Q')[0]


    def _take_samples(self, mm):
        """Get samples for each type of memory transaction.

        Parameters:
          mm: MappedFile representing physical memory
        Returns: dictionary mapping counter type to counter value
        """
        return { c.name: mm.bytes_to_python(c.idx, '=I')[0]
            for c in counters }