You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
232 lines
7.3 KiB
232 lines
7.3 KiB
# 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 }
|