mirror of
				https://github.com/ARM-software/devlib.git
				synced 2025-11-03 23:41:21 +00:00 
			
		
		
		
	@@ -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  |
 | 
			
		||||
+-------------+-------------+---------------+
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user