mirror of
https://github.com/ARM-software/devlib.git
synced 2025-01-31 02:00:45 +00:00
commit
e4fda7898d
@ -19,8 +19,8 @@ from devlib.instrument.monsoon import MonsoonInstrument
|
||||
from devlib.instrument.netstats import NetstatsInstrument
|
||||
from devlib.instrument.gem5power import Gem5PowerInstrument
|
||||
|
||||
from devlib.derived import DerivedMeasurements
|
||||
from devlib.derived.derived_measurements import DerivedEnergyMeasurements
|
||||
from devlib.derived import DerivedMeasurements, DerivedMetric
|
||||
from devlib.derived.energy import DerivedEnergyMeasurements
|
||||
|
||||
from devlib.trace.ftrace import FtraceCollector
|
||||
|
||||
|
@ -12,8 +12,49 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from devlib.instrument import MeasurementType, MEASUREMENT_TYPES
|
||||
|
||||
|
||||
class DerivedMetric(object):
|
||||
|
||||
__slots__ = ['name', 'value', 'measurement_type']
|
||||
|
||||
@property
|
||||
def units(self):
|
||||
return self.measurement_type.units
|
||||
|
||||
def __init__(self, name, value, measurement_type):
|
||||
self.name = name
|
||||
self.value = value
|
||||
if isinstance(measurement_type, MeasurementType):
|
||||
self.measurement_type = measurement_type
|
||||
else:
|
||||
try:
|
||||
self.measurement_type = MEASUREMENT_TYPES[measurement_type]
|
||||
except KeyError:
|
||||
msg = 'Unknown measurement type: {}'
|
||||
raise ValueError(msg.format(measurement_type))
|
||||
|
||||
def __cmp__(self, other):
|
||||
if hasattr(other, 'value'):
|
||||
return cmp(self.value, other.value)
|
||||
else:
|
||||
return cmp(self.value, other)
|
||||
|
||||
def __str__(self):
|
||||
if self.units:
|
||||
return '{}: {} {}'.format(self.name, self.value, self.units)
|
||||
else:
|
||||
return '{}: {}'.format(self.name, self.value)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class DerivedMeasurements(object):
|
||||
|
||||
@staticmethod
|
||||
def process(measurements_csv):
|
||||
raise NotImplementedError()
|
||||
def process(self, measurements_csv):
|
||||
return []
|
||||
|
||||
def process_raw(self, *args):
|
||||
return []
|
||||
|
@ -15,8 +15,8 @@
|
||||
from __future__ import division
|
||||
from collections import defaultdict
|
||||
|
||||
from devlib import DerivedMeasurements
|
||||
from devlib.instrument import Measurement, MEASUREMENT_TYPES, InstrumentChannel
|
||||
from devlib import DerivedMeasurements, DerivedMetric
|
||||
from devlib.instrument import MEASUREMENT_TYPES, InstrumentChannel
|
||||
|
||||
|
||||
class DerivedEnergyMeasurements(DerivedMeasurements):
|
||||
@ -56,7 +56,7 @@ class DerivedEnergyMeasurements(DerivedMeasurements):
|
||||
power_results = defaultdict(float)
|
||||
|
||||
# Process data
|
||||
for count, row in enumerate(measurements_csv.itermeasurements()):
|
||||
for count, row in enumerate(measurements_csv.iter_measurements()):
|
||||
if use_timestamp:
|
||||
last_ts = row_ts
|
||||
row_ts = time_measurment.convert(float(row[ts_index].value), 'time')
|
||||
@ -86,12 +86,12 @@ class DerivedEnergyMeasurements(DerivedMeasurements):
|
||||
derived_measurements = []
|
||||
for site in energy_results:
|
||||
total_energy = energy_results[site]['end'] - energy_results[site]['start']
|
||||
instChannel = InstrumentChannel('cum_energy', site, MEASUREMENT_TYPES['energy'])
|
||||
derived_measurements.append(Measurement(total_energy, instChannel))
|
||||
name = '{}_total_energy'.format(site)
|
||||
derived_measurements.append(DerivedMetric(name, total_energy, MEASUREMENT_TYPES['energy']))
|
||||
|
||||
for site in power_results:
|
||||
power = power_results[site] / (count + 1) #pylint: disable=undefined-loop-variable
|
||||
instChannel = InstrumentChannel('avg_power', site, MEASUREMENT_TYPES['power'])
|
||||
derived_measurements.append(Measurement(power, instChannel))
|
||||
name = '{}_average_power'.format(site)
|
||||
derived_measurements.append(DerivedMetric(name, power, MEASUREMENT_TYPES['power']))
|
||||
|
||||
return derived_measurements
|
@ -72,38 +72,60 @@ class MeasurementType(object):
|
||||
return text.format(self.name, self.units)
|
||||
|
||||
|
||||
# Standard measures
|
||||
# Standard measures. In order to make sure that downstream data processing is not tied
|
||||
# to particular insturments (e.g. a particular method of mearuing power), instruments
|
||||
# must, where possible, resport their measurments formatted as on of the standard types
|
||||
# defined here.
|
||||
_measurement_types = [
|
||||
# For whatever reason, the type of measurement could not be established.
|
||||
MeasurementType('unknown', None),
|
||||
MeasurementType('time', 'seconds',
|
||||
|
||||
# Generic measurements
|
||||
MeasurementType('count', 'count'),
|
||||
MeasurementType('percent', 'percent'),
|
||||
|
||||
# Time measurement. While there is typically a single "canonical" unit
|
||||
# used for each type of measurmenent, time may be measured to a wide variety
|
||||
# of events occuring at a wide range of scales. Forcing everying into a
|
||||
# single scale will lead to inefficient and awkward to work with result tables.
|
||||
# Coversion functions between the formats are specified, so that downstream
|
||||
# processors that expect all times time be at a particular scale can automatically
|
||||
# covert without being familar with individual instruments.
|
||||
MeasurementType('time', 'seconds', 'time',
|
||||
conversions={
|
||||
'time_us': lambda x: x * 1000000,
|
||||
'time_ms': lambda x: x * 1000,
|
||||
}
|
||||
),
|
||||
MeasurementType('time_us', 'microseconds',
|
||||
MeasurementType('time_us', 'microseconds', 'time',
|
||||
conversions={
|
||||
'time': lambda x: x / 1000000,
|
||||
'time_ms': lambda x: x / 1000,
|
||||
}
|
||||
),
|
||||
MeasurementType('time_ms', 'milliseconds',
|
||||
MeasurementType('time_ms', 'milliseconds', 'time',
|
||||
conversions={
|
||||
'time': lambda x: x / 1000,
|
||||
'time_us': lambda x: x * 1000,
|
||||
}
|
||||
),
|
||||
MeasurementType('temperature', 'degrees'),
|
||||
|
||||
# Measurements related to thermals.
|
||||
MeasurementType('temperature', 'degrees', 'thermal'),
|
||||
|
||||
# Measurements related to power end energy consumption.
|
||||
MeasurementType('power', 'watts', 'power/energy'),
|
||||
MeasurementType('voltage', 'volts', 'power/energy'),
|
||||
MeasurementType('current', 'amps', 'power/energy'),
|
||||
MeasurementType('energy', 'joules', 'power/energy'),
|
||||
|
||||
# Measurments realted to data transfer, e.g. neworking,
|
||||
# memory, or backing storage.
|
||||
MeasurementType('tx', 'bytes', 'data transfer'),
|
||||
MeasurementType('rx', 'bytes', 'data transfer'),
|
||||
MeasurementType('tx/rx', 'bytes', 'data transfer'),
|
||||
|
||||
MeasurementType('fps', 'fps', 'ui render'),
|
||||
MeasurementType('frames', 'frames', 'ui render'),
|
||||
]
|
||||
for m in _measurement_types:
|
||||
@ -127,7 +149,7 @@ class Measurement(object):
|
||||
self.channel = channel
|
||||
|
||||
def __cmp__(self, other):
|
||||
if isinstance(other, Measurement):
|
||||
if hasattr(other, 'value'):
|
||||
return cmp(self.value, other.value)
|
||||
else:
|
||||
return cmp(self.value, other)
|
||||
@ -147,26 +169,32 @@ class MeasurementsCsv(object):
|
||||
self.path = path
|
||||
self.channels = channels
|
||||
self.sample_rate_hz = sample_rate_hz
|
||||
self._fh = open(path, 'rb')
|
||||
if self.channels is None:
|
||||
self._load_channels()
|
||||
headings = [chan.label for chan in self.channels]
|
||||
self.data_tuple = collections.namedtuple('csv_entry', headings)
|
||||
|
||||
def measurements(self):
|
||||
return list(self.itermeasurements())
|
||||
return list(self.iter_measurements())
|
||||
|
||||
def itermeasurements(self):
|
||||
self._fh.seek(0)
|
||||
reader = csv.reader(self._fh)
|
||||
reader.next() # headings
|
||||
for row in reader:
|
||||
def iter_measurements(self):
|
||||
for row in self._iter_rows():
|
||||
values = map(numeric, row)
|
||||
yield [Measurement(v, c) for (v, c) in zip(values, self.channels)]
|
||||
|
||||
def values(self):
|
||||
return list(self.iter_values())
|
||||
|
||||
def iter_values(self):
|
||||
for row in self._iter_rows():
|
||||
values = map(numeric, row)
|
||||
yield self.data_tuple(*values)
|
||||
|
||||
def _load_channels(self):
|
||||
self._fh.seek(0)
|
||||
reader = csv.reader(self._fh)
|
||||
header = reader.next()
|
||||
self._fh.seek(0)
|
||||
header = []
|
||||
with open(self.path, 'rb') as fh:
|
||||
reader = csv.reader(fh)
|
||||
header = reader.next()
|
||||
|
||||
self.channels = []
|
||||
for entry in header:
|
||||
@ -175,22 +203,35 @@ class MeasurementsCsv(object):
|
||||
if entry.endswith(suffix):
|
||||
site = entry[:-len(suffix)]
|
||||
measure = mt
|
||||
name = '{}_{}'.format(site, measure)
|
||||
break
|
||||
else:
|
||||
site = entry
|
||||
measure = 'unknown'
|
||||
name = entry
|
||||
if entry in MEASUREMENT_TYPES:
|
||||
site = None
|
||||
measure = entry
|
||||
else:
|
||||
site = entry
|
||||
measure = 'unknown'
|
||||
|
||||
chan = InstrumentChannel(name, site, measure)
|
||||
chan = InstrumentChannel(site, measure)
|
||||
self.channels.append(chan)
|
||||
|
||||
def _iter_rows(self):
|
||||
with open(self.path, 'rb') as fh:
|
||||
reader = csv.reader(fh)
|
||||
reader.next() # headings
|
||||
for row in reader:
|
||||
yield row
|
||||
|
||||
|
||||
class InstrumentChannel(object):
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
return '{}_{}'.format(self.site, self.kind)
|
||||
if self.site is not None:
|
||||
return '{}_{}'.format(self.site, self.kind)
|
||||
return self.kind
|
||||
|
||||
name = label
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
@ -200,8 +241,7 @@ class InstrumentChannel(object):
|
||||
def units(self):
|
||||
return self.measurement_type.units
|
||||
|
||||
def __init__(self, name, site, measurement_type, **attrs):
|
||||
self.name = name
|
||||
def __init__(self, site, measurement_type, **attrs):
|
||||
self.site = site
|
||||
if isinstance(measurement_type, MeasurementType):
|
||||
self.measurement_type = measurement_type
|
||||
@ -243,10 +283,8 @@ class Instrument(object):
|
||||
measure = measure.name
|
||||
return [c for c in self.list_channels() if c.kind == measure]
|
||||
|
||||
def add_channel(self, site, measure, name=None, **attrs):
|
||||
if name is None:
|
||||
name = '{}_{}'.format(site, measure)
|
||||
chan = InstrumentChannel(name, site, measure, **attrs)
|
||||
def add_channel(self, site, measure, **attrs):
|
||||
chan = InstrumentChannel(site, measure, **attrs)
|
||||
self.channels[chan.label] = chan
|
||||
|
||||
# initialization and teardown
|
||||
@ -297,3 +335,6 @@ class Instrument(object):
|
||||
|
||||
def get_data(self, outfile):
|
||||
pass
|
||||
|
||||
def get_raw(self):
|
||||
return []
|
||||
|
@ -121,3 +121,6 @@ class AcmeCapeInstrument(Instrument):
|
||||
output_row.append(float(row[i])/1000)
|
||||
writer.writerow(output_row)
|
||||
return MeasurementsCsv(outfile, self.active_channels, self.sample_rate_hz)
|
||||
|
||||
def get_raw(self):
|
||||
return [self.raw_data_file]
|
||||
|
@ -33,6 +33,7 @@ class DaqInstrument(Instrument):
|
||||
# pylint: disable=no-member
|
||||
super(DaqInstrument, self).__init__(target)
|
||||
self._need_reset = True
|
||||
self._raw_files = []
|
||||
if execute_command is None:
|
||||
raise HostError('Could not import "daqpower": {}'.format(import_error_mesg))
|
||||
if labels is None:
|
||||
@ -68,6 +69,7 @@ class DaqInstrument(Instrument):
|
||||
if not result.status == Status.OK: # pylint: disable=no-member
|
||||
raise HostError(result.message)
|
||||
self._need_reset = False
|
||||
self._raw_files = []
|
||||
|
||||
def start(self):
|
||||
if self._need_reset:
|
||||
@ -86,6 +88,7 @@ class DaqInstrument(Instrument):
|
||||
site = os.path.splitext(entry)[0]
|
||||
path = os.path.join(tempdir, entry)
|
||||
raw_file_map[site] = path
|
||||
self._raw_files.append(path)
|
||||
|
||||
active_sites = unique([c.site for c in self.active_channels])
|
||||
file_handles = []
|
||||
@ -131,6 +134,9 @@ class DaqInstrument(Instrument):
|
||||
for fh in file_handles:
|
||||
fh.close()
|
||||
|
||||
def get_raw(self):
|
||||
return self._raw_files
|
||||
|
||||
def teardown(self):
|
||||
self.execute('close')
|
||||
|
||||
|
@ -52,6 +52,7 @@ class EnergyProbeInstrument(Instrument):
|
||||
self.raw_output_directory = None
|
||||
self.process = None
|
||||
self.sample_rate_hz = 10000 # Determined empirically
|
||||
self.raw_data_file = None
|
||||
|
||||
for label in self.labels:
|
||||
for kind in self.attributes:
|
||||
@ -64,6 +65,7 @@ class EnergyProbeInstrument(Instrument):
|
||||
for i, rval in enumerate(self.resistor_values)]
|
||||
rstring = ''.join(parts)
|
||||
self.command = '{} -d {} -l {} {}'.format(self.caiman, self.device_entry, rstring, self.raw_output_directory)
|
||||
self.raw_data_file = None
|
||||
|
||||
def start(self):
|
||||
self.logger.debug(self.command)
|
||||
@ -92,10 +94,10 @@ class EnergyProbeInstrument(Instrument):
|
||||
num_of_ports = len(self.resistor_values)
|
||||
struct_format = '{}I'.format(num_of_ports * self.attributes_per_sample)
|
||||
not_a_full_row_seen = False
|
||||
raw_data_file = os.path.join(self.raw_output_directory, '0000000000')
|
||||
self.raw_data_file = os.path.join(self.raw_output_directory, '0000000000')
|
||||
|
||||
self.logger.debug('Parsing raw data file: {}'.format(raw_data_file))
|
||||
with open(raw_data_file, 'rb') as bfile:
|
||||
self.logger.debug('Parsing raw data file: {}'.format(self.raw_data_file))
|
||||
with open(self.raw_data_file, 'rb') as bfile:
|
||||
with open(outfile, 'wb') as wfh:
|
||||
writer = csv.writer(wfh)
|
||||
writer.writerow(active_channels)
|
||||
@ -114,3 +116,6 @@ class EnergyProbeInstrument(Instrument):
|
||||
else:
|
||||
not_a_full_row_seen = True
|
||||
return MeasurementsCsv(outfile, self.active_channels, self.sample_rate_hz)
|
||||
|
||||
def get_raw(self):
|
||||
return [self.raw_data_file]
|
||||
|
@ -20,6 +20,7 @@ class FramesInstrument(Instrument):
|
||||
self.collector = None
|
||||
self.header = None
|
||||
self._need_reset = True
|
||||
self._raw_file = None
|
||||
self._init_channels()
|
||||
|
||||
def reset(self, sites=None, kinds=None, channels=None):
|
||||
@ -27,6 +28,7 @@ class FramesInstrument(Instrument):
|
||||
self.collector = self.collector_cls(self.target, self.period,
|
||||
self.collector_target, self.header)
|
||||
self._need_reset = False
|
||||
self._raw_file = None
|
||||
|
||||
def start(self):
|
||||
if self._need_reset:
|
||||
@ -38,14 +40,16 @@ class FramesInstrument(Instrument):
|
||||
self._need_reset = True
|
||||
|
||||
def get_data(self, outfile):
|
||||
raw_outfile = None
|
||||
if self.keep_raw:
|
||||
raw_outfile = outfile + '.raw'
|
||||
self.collector.process_frames(raw_outfile)
|
||||
self._raw_file = outfile + '.raw'
|
||||
self.collector.process_frames(self._raw_file)
|
||||
active_sites = [chan.label for chan in self.active_channels]
|
||||
self.collector.write_frames(outfile, columns=active_sites)
|
||||
return MeasurementsCsv(outfile, self.active_channels, self.sample_rate_hz)
|
||||
|
||||
def get_raw(self):
|
||||
return [self._raw_file] if self._raw_file else []
|
||||
|
||||
def _init_channels(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
@ -45,7 +45,7 @@ class HwmonInstrument(Instrument):
|
||||
measure = self.measure_map.get(ts.kind)[0]
|
||||
if measure:
|
||||
self.logger.debug('\tAdding sensor {}'.format(ts.name))
|
||||
self.add_channel(_guess_site(ts), measure, name=ts.name, sensor=ts)
|
||||
self.add_channel(_guess_site(ts), measure, sensor=ts)
|
||||
else:
|
||||
self.logger.debug('\tSkipping sensor {} (unknown kind "{}")'.format(ts.name, ts.kind))
|
||||
except ValueError:
|
||||
|
@ -210,22 +210,22 @@ class JunoEnergyInstrument(Instrument):
|
||||
mode = CONTINUOUS | INSTANTANEOUS
|
||||
|
||||
_channels = [
|
||||
InstrumentChannel('sys_curr', 'sys', 'current'),
|
||||
InstrumentChannel('a57_curr', 'a57', 'current'),
|
||||
InstrumentChannel('a53_curr', 'a53', 'current'),
|
||||
InstrumentChannel('gpu_curr', 'gpu', 'current'),
|
||||
InstrumentChannel('sys_volt', 'sys', 'voltage'),
|
||||
InstrumentChannel('a57_volt', 'a57', 'voltage'),
|
||||
InstrumentChannel('a53_volt', 'a53', 'voltage'),
|
||||
InstrumentChannel('gpu_volt', 'gpu', 'voltage'),
|
||||
InstrumentChannel('sys_pow', 'sys', 'power'),
|
||||
InstrumentChannel('a57_pow', 'a57', 'power'),
|
||||
InstrumentChannel('a53_pow', 'a53', 'power'),
|
||||
InstrumentChannel('gpu_pow', 'gpu', 'power'),
|
||||
InstrumentChannel('sys_cenr', 'sys', 'energy'),
|
||||
InstrumentChannel('a57_cenr', 'a57', 'energy'),
|
||||
InstrumentChannel('a53_cenr', 'a53', 'energy'),
|
||||
InstrumentChannel('gpu_cenr', 'gpu', 'energy'),
|
||||
InstrumentChannel('sys', 'current'),
|
||||
InstrumentChannel('a57', 'current'),
|
||||
InstrumentChannel('a53', 'current'),
|
||||
InstrumentChannel('gpu', 'current'),
|
||||
InstrumentChannel('sys', 'voltage'),
|
||||
InstrumentChannel('a57', 'voltage'),
|
||||
InstrumentChannel('a53', 'voltage'),
|
||||
InstrumentChannel('gpu', 'voltage'),
|
||||
InstrumentChannel('sys', 'power'),
|
||||
InstrumentChannel('a57', 'power'),
|
||||
InstrumentChannel('a53', 'power'),
|
||||
InstrumentChannel('gpu', 'power'),
|
||||
InstrumentChannel('sys', 'energy'),
|
||||
InstrumentChannel('a57', 'energy'),
|
||||
InstrumentChannel('a53', 'energy'),
|
||||
InstrumentChannel('gpu', 'energy'),
|
||||
]
|
||||
|
||||
def __init__(self, target):
|
||||
|
@ -1011,11 +1011,12 @@ class AndroidTarget(Target):
|
||||
self.uninstall_executable(name)
|
||||
|
||||
def get_pids_of(self, process_name):
|
||||
result = self.execute('ps {}'.format(process_name[-15:]), check_exit_code=False).strip()
|
||||
if result and 'not found' not in result:
|
||||
return [int(x.split()[1]) for x in result.split('\n')[1:]]
|
||||
else:
|
||||
return []
|
||||
result = []
|
||||
search_term = process_name[-15:]
|
||||
for entry in self.ps():
|
||||
if search_term in entry.name:
|
||||
result.append(entry.pid)
|
||||
return result
|
||||
|
||||
def ps(self, **kwargs):
|
||||
lines = iter(convert_new_lines(self.execute('ps')).split('\n'))
|
||||
@ -1023,8 +1024,12 @@ class AndroidTarget(Target):
|
||||
result = []
|
||||
for line in lines:
|
||||
parts = line.split(None, 8)
|
||||
if parts:
|
||||
result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))
|
||||
if not parts:
|
||||
continue
|
||||
if len(parts) == 8:
|
||||
# wchan was blank; insert an empty field where it should be.
|
||||
parts.insert(5, '')
|
||||
result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))
|
||||
if not kwargs:
|
||||
return result
|
||||
else:
|
||||
|
@ -103,6 +103,9 @@ CPU_PART_MAP = {
|
||||
0x211: {0x1: 'KryoGold'},
|
||||
0x800: {None: 'Falkor'},
|
||||
},
|
||||
0x53: { # Samsung LSI
|
||||
0x001: {0x1: 'MongooseM1'},
|
||||
},
|
||||
0x56: { # Marvell
|
||||
0x131: {
|
||||
0x2: 'Feroceon 88F6281',
|
||||
|
@ -83,9 +83,14 @@ class FrameCollector(threading.Thread):
|
||||
header = self.header
|
||||
frames = self.frames
|
||||
else:
|
||||
header = [c for c in self.header if c in columns]
|
||||
indexes = [self.header.index(c) for c in header]
|
||||
indexes = []
|
||||
for c in columns:
|
||||
if c not in self.header:
|
||||
msg = 'Invalid column "{}"; must be in {}'
|
||||
raise ValueError(msg.format(c, self.header))
|
||||
indexes.append(self.header.index(c))
|
||||
frames = [[f[i] for i in indexes] for f in self.frames]
|
||||
header = columns
|
||||
with open(outfile, 'w') as wfh:
|
||||
writer = csv.writer(wfh)
|
||||
if header:
|
||||
@ -122,7 +127,8 @@ class SurfaceFlingerFrameCollector(FrameCollector):
|
||||
return self.target.execute(cmd.format(activity))
|
||||
|
||||
def list(self):
|
||||
return self.target.execute('dumpsys SurfaceFlinger --list').split('\r\n')
|
||||
text = self.target.execute('dumpsys SurfaceFlinger --list')
|
||||
return text.replace('\r\n', '\n').replace('\r', '\n').split('\n')
|
||||
|
||||
def _process_raw_file(self, fh):
|
||||
text = fh.read().replace('\r\n', '\n').replace('\r', '\n')
|
||||
@ -203,3 +209,43 @@ class GfxinfoFrameCollector(FrameCollector):
|
||||
if not found:
|
||||
logger.warning('Could not find frames data in gfxinfo output')
|
||||
return
|
||||
|
||||
|
||||
def _file_reverse_iter(fh, buf_size=1024):
|
||||
fh.seek(0, os.SEEK_END)
|
||||
offset = 0
|
||||
file_size = remaining_size = fh.tell()
|
||||
while remaining_size > 0:
|
||||
offset = min(file_size, offset + buf_size)
|
||||
fh.seek(file_size - offset)
|
||||
buf = fh.read(min(remaining_size, buf_size))
|
||||
remaining_size -= buf_size
|
||||
yield buf
|
||||
|
||||
|
||||
def gfxinfo_get_last_dump(filepath):
|
||||
"""
|
||||
Return the last gfxinfo dump from the frame collector's raw output.
|
||||
|
||||
"""
|
||||
record = ''
|
||||
with open(filepath, 'r') as fh:
|
||||
fh_iter = _file_reverse_iter(fh)
|
||||
try:
|
||||
while True:
|
||||
buf = fh_iter.next()
|
||||
ix = buf.find('** Graphics')
|
||||
if ix >= 0:
|
||||
return buf[ix:] + record
|
||||
|
||||
ix = buf.find(' **\n')
|
||||
if ix >= 0:
|
||||
buf = fh_iter.next() + buf
|
||||
ix = buf.find('** Graphics')
|
||||
if ix < 0:
|
||||
msg = '"{}" appears to be corrupted'
|
||||
raise RuntimeError(msg.format(filepath))
|
||||
return buf[ix:] + record
|
||||
record = buf + record
|
||||
except StopIteration:
|
||||
pass
|
||||
|
@ -9,7 +9,7 @@ Example
|
||||
-------
|
||||
|
||||
The following example shows how to use an implementation of a
|
||||
:class:`DerivedMeasurement` to obtain a list of calculated ``Measurements``.
|
||||
:class:`DerivedMeasurement` to obtain a list of calculated ``DerivedMetric``'s.
|
||||
|
||||
.. code-block:: ipython
|
||||
|
||||
@ -35,35 +35,92 @@ API
|
||||
Derived Measurements
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. class:: DerivedMeasurements()
|
||||
.. class:: DerivedMeasurements
|
||||
|
||||
The ``DerivedMeasurements`` class is an abstract base for implementing
|
||||
additional classes to calculate various metrics.
|
||||
The ``DerivedMeasurements`` class provides an API for post-processing
|
||||
instrument output offline (i.e. without a connection to the target device) to
|
||||
generate additional metrics.
|
||||
|
||||
.. method:: DerivedMeasurements.process(measurement_csv)
|
||||
|
||||
Returns a list of :class:`Measurement` objects that have been calculated.
|
||||
Process a :class:`MeasurementsCsv`, returning a list of
|
||||
:class:`DerivedMetric` and/or :class:`MeasurementsCsv` objects that have been
|
||||
derived from the input. The exact nature and ordering of the list memebers
|
||||
is specific to indivial 'class'`DerivedMeasurements` implementations.
|
||||
|
||||
.. method:: DerivedMeasurements.process_raw(\*args)
|
||||
|
||||
Process raw output from an instrument, returnin a list :class:`DerivedMetric`
|
||||
and/or :class:`MeasurementsCsv` objects that have been derived from the
|
||||
input. The exact nature and ordering of the list memebers is specific to
|
||||
indivial 'class'`DerivedMeasurements` implewmentations.
|
||||
|
||||
The arguents to this method should be paths to raw output files generated by
|
||||
an instrument. The number and order of expected arguments is specific to
|
||||
particular implmentations.
|
||||
|
||||
|
||||
Derived Metric
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. class:: DerivedMetric
|
||||
|
||||
Represents a metric derived from previously collected ``Measurement``s.
|
||||
Unlike, a ``Measurement``, this was not measured directly from the target.
|
||||
|
||||
|
||||
.. attribute:: DerivedMetric.name
|
||||
|
||||
The name of the derived metric. This uniquely defines a metric -- two
|
||||
``DerivedMetric`` objects with the same ``name`` represent to instances of
|
||||
the same metric (e.g. computed from two different inputs).
|
||||
|
||||
.. attribute:: DerivedMetric.value
|
||||
|
||||
The ``numeric`` value of the metric that has been computed for a particular
|
||||
input.
|
||||
|
||||
.. attribute:: DerivedMetric.measurement_type
|
||||
|
||||
The ``MeasurementType`` of the metric. This indicates which conceptual
|
||||
category the metric falls into, its units, and conversions to other
|
||||
measurement types.
|
||||
|
||||
.. attribute:: DerivedMetric.units
|
||||
|
||||
The units in which the metric's value is expressed.
|
||||
|
||||
|
||||
Available Derived Measurements
|
||||
-------------------------------
|
||||
.. class:: DerivedEnergyMeasurements()
|
||||
|
||||
The ``DerivedEnergyMeasurements`` class is used to calculate average power and
|
||||
cumulative energy for each site if the required data is present.
|
||||
.. note:: If a method of the API is not documented for a particular
|
||||
implementation, that means that it s not overriden by that
|
||||
implementation. It is still safe to call it -- an empty list will be
|
||||
returned.
|
||||
|
||||
The calculation of cumulative energy can occur in 3 ways. If a
|
||||
``site`` contains ``energy`` results, the first and last measurements are extracted
|
||||
and the delta calculated. If not, a ``timestamp`` channel will be used to calculate
|
||||
the energy from the power channel, failing back to using the sample rate attribute
|
||||
of the :class:`MeasurementCsv` file if timestamps are not available. If neither
|
||||
timestamps or a sample rate are available then an error will be raised.
|
||||
Energy
|
||||
~~~~~~
|
||||
|
||||
.. class:: DerivedEnergyMeasurements
|
||||
|
||||
The ``DerivedEnergyMeasurements`` class is used to calculate average power and
|
||||
cumulative energy for each site if the required data is present.
|
||||
|
||||
The calculation of cumulative energy can occur in 3 ways. If a
|
||||
``site`` contains ``energy`` results, the first and last measurements are extracted
|
||||
and the delta calculated. If not, a ``timestamp`` channel will be used to calculate
|
||||
the energy from the power channel, failing back to using the sample rate attribute
|
||||
of the :class:`MeasurementCsv` file if timestamps are not available. If neither
|
||||
timestamps or a sample rate are available then an error will be raised.
|
||||
|
||||
|
||||
.. method:: DerivedEnergyMeasurements.process(measurement_csv)
|
||||
|
||||
Returns a list of :class:`Measurement` objects that have been calculated for
|
||||
the average power and cumulative energy for each site.
|
||||
|
||||
This will return total cumulative energy for each energy channel, and the
|
||||
average power for each power channel in the input CSV. The output will contain
|
||||
all energy metrics followed by power metrics. The ordering of both will match
|
||||
the ordering of channels in the input. The metrics will by named based on the
|
||||
sites of the coresponding channels according to the following patters:
|
||||
``"<site>_total_energy"`` and ``"<site>_average_power"``.
|
||||
|
||||
|
@ -65,8 +65,8 @@ Instrument
|
||||
:INSTANTANEOUS: The instrument supports taking a single sample via
|
||||
``take_measurement()``.
|
||||
:CONTINUOUS: The instrument supports collecting measurements over a
|
||||
period of time via ``start()``, ``stop()``, and
|
||||
``get_data()`` methods.
|
||||
period of time via ``start()``, ``stop()``, ``get_data()``,
|
||||
and (optionally) ``get_raw`` methods.
|
||||
|
||||
.. note:: It's possible for one instrument to support more than a single
|
||||
mode.
|
||||
@ -161,6 +161,13 @@ Instrument
|
||||
.. note:: This method is only implemented by :class:`Instrument`\ s that
|
||||
support ``CONTINUOUS`` measurement.
|
||||
|
||||
.. method:: Instrument.get_raw()
|
||||
|
||||
Returns a list of paths to files containing raw output from the underlying
|
||||
source(s) that is used to produce the data CSV. If now raw output is
|
||||
generated or saved, an empty list will be returned. The format of the
|
||||
contents of the raw files is entirely source-dependent.
|
||||
|
||||
.. attribute:: Instrument.sample_rate_hz
|
||||
|
||||
Sample rate of the instrument in Hz. Assumed to be the same for all channels.
|
||||
@ -229,13 +236,15 @@ defined measurement types are
|
||||
+-------------+-------------+---------------+
|
||||
| name | units | category |
|
||||
+=============+=============+===============+
|
||||
| time | seconds | |
|
||||
| count | count | |
|
||||
+-------------+-------------+---------------+
|
||||
| time | microseconds| |
|
||||
| percent | percent | |
|
||||
+-------------+-------------+---------------+
|
||||
| time | milliseconds| |
|
||||
| time_us | microseconds| time |
|
||||
+-------------+-------------+---------------+
|
||||
| temperature | degrees | |
|
||||
| time_ms | milliseconds| time |
|
||||
+-------------+-------------+---------------+
|
||||
| temperature | degrees | thermal |
|
||||
+-------------+-------------+---------------+
|
||||
| power | watts | power/energy |
|
||||
+-------------+-------------+---------------+
|
||||
|
Loading…
x
Reference in New Issue
Block a user