mirror of
				https://github.com/ARM-software/devlib.git
				synced 2025-11-04 07:51:21 +00:00 
			
		
		
		
	@@ -13,6 +13,7 @@ from devlib.instrument import Instrument, InstrumentChannel, Measurement, Measur
 | 
				
			|||||||
from devlib.instrument import MEASUREMENT_TYPES, INSTANTANEOUS, CONTINUOUS
 | 
					from devlib.instrument import MEASUREMENT_TYPES, INSTANTANEOUS, CONTINUOUS
 | 
				
			||||||
from devlib.instrument.daq import DaqInstrument
 | 
					from devlib.instrument.daq import DaqInstrument
 | 
				
			||||||
from devlib.instrument.energy_probe import EnergyProbeInstrument
 | 
					from devlib.instrument.energy_probe import EnergyProbeInstrument
 | 
				
			||||||
 | 
					from devlib.instrument.frames import GfxInfoFramesInstrument
 | 
				
			||||||
from devlib.instrument.hwmon import HwmonInstrument
 | 
					from devlib.instrument.hwmon import HwmonInstrument
 | 
				
			||||||
from devlib.instrument.monsoon import MonsoonInstrument
 | 
					from devlib.instrument.monsoon import MonsoonInstrument
 | 
				
			||||||
from devlib.instrument.netstats import NetstatsInstrument
 | 
					from devlib.instrument.netstats import NetstatsInstrument
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,6 @@
 | 
				
			|||||||
# limitations under the License.
 | 
					# limitations under the License.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
class DevlibError(Exception):
 | 
					class DevlibError(Exception):
 | 
				
			||||||
    """Base class for all Devlib exceptions."""
 | 
					    """Base class for all Devlib exceptions."""
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
@@ -49,3 +48,42 @@ class TimeoutError(DevlibError):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return '\n'.join([self.message, 'OUTPUT:', self.output or ''])
 | 
					        return '\n'.join([self.message, 'OUTPUT:', self.output or ''])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WorkerThreadError(DevlibError):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This should get raised  in the main thread if a non-WAError-derived
 | 
				
			||||||
 | 
					    exception occurs on a worker/background thread. If a WAError-derived
 | 
				
			||||||
 | 
					    exception is raised in the worker, then it that exception should be
 | 
				
			||||||
 | 
					    re-raised on the main thread directly -- the main point of this is to
 | 
				
			||||||
 | 
					    preserve the backtrace in the output, and backtrace doesn't get output for
 | 
				
			||||||
 | 
					    WAErrors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, thread, exc_info):
 | 
				
			||||||
 | 
					        self.thread = thread
 | 
				
			||||||
 | 
					        self.exc_info = exc_info
 | 
				
			||||||
 | 
					        orig = self.exc_info[1]
 | 
				
			||||||
 | 
					        orig_name = type(orig).__name__
 | 
				
			||||||
 | 
					        message = 'Exception of type {} occured on thread {}:\n'.format(orig_name, thread)
 | 
				
			||||||
 | 
					        message += '{}\n{}: {}'.format(get_traceback(self.exc_info), orig_name, orig)
 | 
				
			||||||
 | 
					        super(WorkerThreadError, self).__init__(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_traceback(exc=None):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Returns the string with the traceback for the specifiec exc
 | 
				
			||||||
 | 
					    object, or for the current exception exc is not specified.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    import StringIO, traceback, sys
 | 
				
			||||||
 | 
					    if exc is None:
 | 
				
			||||||
 | 
					        exc = sys.exc_info()
 | 
				
			||||||
 | 
					    if not exc:
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					    tb = exc[2]
 | 
				
			||||||
 | 
					    sio = StringIO.StringIO()
 | 
				
			||||||
 | 
					    traceback.print_tb(tb, file=sio)
 | 
				
			||||||
 | 
					    del tb  # needs to be done explicitly see: http://docs.python.org/2/library/sys.html#sys.exc_info
 | 
				
			||||||
 | 
					    return sio.getvalue()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@
 | 
				
			|||||||
# See the License for the specific language governing permissions and
 | 
					# See the License for the specific language governing permissions and
 | 
				
			||||||
# limitations under the License.
 | 
					# limitations under the License.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					from __future__ import division
 | 
				
			||||||
import csv
 | 
					import csv
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import collections
 | 
					import collections
 | 
				
			||||||
@@ -24,28 +25,33 @@ from devlib.utils.types import numeric
 | 
				
			|||||||
INSTANTANEOUS = 1
 | 
					INSTANTANEOUS = 1
 | 
				
			||||||
CONTINUOUS = 2
 | 
					CONTINUOUS = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MEASUREMENT_TYPES = {}  # populated further down
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MeasurementType(tuple):
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    __slots__ = []
 | 
					class MeasurementType(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __new__(cls, name, units, category=None):
 | 
					    def __init__(self, name, units, category=None, conversions=None):
 | 
				
			||||||
        return tuple.__new__(cls, (name, units, category))
 | 
					        self.name = name
 | 
				
			||||||
 | 
					        self.units = units
 | 
				
			||||||
 | 
					        self.category = category
 | 
				
			||||||
 | 
					        self.conversions = {}
 | 
				
			||||||
 | 
					        if conversions is not None:
 | 
				
			||||||
 | 
					            for key, value in conversions.iteritems():
 | 
				
			||||||
 | 
					                if not callable(value):
 | 
				
			||||||
 | 
					                    msg = 'Converter must be callable; got {} "{}"'
 | 
				
			||||||
 | 
					                    raise ValueError(msg.format(type(value), value))
 | 
				
			||||||
 | 
					                self.conversions[key] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    def convert(self, value, to):
 | 
				
			||||||
    def name(self):
 | 
					        if isinstance(to, basestring) and to in MEASUREMENT_TYPES:
 | 
				
			||||||
        return tuple.__getitem__(self, 0)
 | 
					            to = MEASUREMENT_TYPES[to]
 | 
				
			||||||
 | 
					        if not isinstance(to, MeasurementType):
 | 
				
			||||||
    @property
 | 
					            msg = 'Unexpected conversion target: "{}"'
 | 
				
			||||||
    def units(self):
 | 
					            raise ValueError(msg.format(to))
 | 
				
			||||||
        return tuple.__getitem__(self, 1)
 | 
					        if not to.name in self.conversions:
 | 
				
			||||||
 | 
					            msg = 'No conversion from {} to {} available'
 | 
				
			||||||
    @property
 | 
					            raise ValueError(msg.format(self.name, to.name))
 | 
				
			||||||
    def category(self):
 | 
					        return self.conversions[to.name](value)
 | 
				
			||||||
        return tuple.__getitem__(self, 2)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __getitem__(self, item):
 | 
					 | 
				
			||||||
        raise TypeError()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __cmp__(self, other):
 | 
					    def __cmp__(self, other):
 | 
				
			||||||
        if isinstance(other, MeasurementType):
 | 
					        if isinstance(other, MeasurementType):
 | 
				
			||||||
@@ -55,12 +61,28 @@ class MeasurementType(tuple):
 | 
				
			|||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return self.name
 | 
					        return self.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    __repr__ = __str__
 | 
					    def __repr__(self):
 | 
				
			||||||
 | 
					        if self.category:
 | 
				
			||||||
 | 
					            text = 'MeasurementType({}, {}, {})'
 | 
				
			||||||
 | 
					            return text.format(self.name, self.units, self.category)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            text = 'MeasurementType({}, {})'
 | 
				
			||||||
 | 
					            return text.format(self.name, self.units)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Standard measures
 | 
					# Standard measures
 | 
				
			||||||
_measurement_types = [
 | 
					_measurement_types = [
 | 
				
			||||||
    MeasurementType('time', 'seconds'),
 | 
					    MeasurementType('unknown', None),
 | 
				
			||||||
 | 
					    MeasurementType('time', 'seconds',
 | 
				
			||||||
 | 
					        conversions={
 | 
				
			||||||
 | 
					            'time_us': lambda x: x * 1000,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    MeasurementType('time_us', 'microseconds',
 | 
				
			||||||
 | 
					        conversions={
 | 
				
			||||||
 | 
					            'time': lambda x: x / 1000,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    MeasurementType('temperature', 'degrees'),
 | 
					    MeasurementType('temperature', 'degrees'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MeasurementType('power', 'watts', 'power/energy'),
 | 
					    MeasurementType('power', 'watts', 'power/energy'),
 | 
				
			||||||
@@ -71,8 +93,11 @@ _measurement_types = [
 | 
				
			|||||||
    MeasurementType('tx', 'bytes', 'data transfer'),
 | 
					    MeasurementType('tx', 'bytes', 'data transfer'),
 | 
				
			||||||
    MeasurementType('rx', 'bytes', 'data transfer'),
 | 
					    MeasurementType('rx', 'bytes', 'data transfer'),
 | 
				
			||||||
    MeasurementType('tx/rx', 'bytes', 'data transfer'),
 | 
					    MeasurementType('tx/rx', 'bytes', 'data transfer'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MeasurementType('frames', 'frames', 'ui render'),
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
MEASUREMENT_TYPES = {m.name: m for m in _measurement_types}
 | 
					for m in _measurement_types:
 | 
				
			||||||
 | 
					    MEASUREMENT_TYPES[m.name] = m
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Measurement(object):
 | 
					class Measurement(object):
 | 
				
			||||||
@@ -108,10 +133,12 @@ class Measurement(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class MeasurementsCsv(object):
 | 
					class MeasurementsCsv(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, path, channels):
 | 
					    def __init__(self, path, channels=None):
 | 
				
			||||||
        self.path = path
 | 
					        self.path = path
 | 
				
			||||||
        self.channels = channels
 | 
					        self.channels = channels
 | 
				
			||||||
        self._fh = open(path, 'rb')
 | 
					        self._fh = open(path, 'rb')
 | 
				
			||||||
 | 
					        if self.channels is None:
 | 
				
			||||||
 | 
					            self._load_channels()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def measurements(self):
 | 
					    def measurements(self):
 | 
				
			||||||
        return list(self.itermeasurements())
 | 
					        return list(self.itermeasurements())
 | 
				
			||||||
@@ -124,6 +151,29 @@ class MeasurementsCsv(object):
 | 
				
			|||||||
            values = map(numeric, row)
 | 
					            values = map(numeric, row)
 | 
				
			||||||
            yield [Measurement(v, c) for (v, c) in zip(values, self.channels)]
 | 
					            yield [Measurement(v, c) for (v, c) in zip(values, self.channels)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _load_channels(self):
 | 
				
			||||||
 | 
					        self._fh.seek(0)
 | 
				
			||||||
 | 
					        reader = csv.reader(self._fh)
 | 
				
			||||||
 | 
					        header = reader.next()
 | 
				
			||||||
 | 
					        self._fh.seek(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.channels = []
 | 
				
			||||||
 | 
					        for entry in header:
 | 
				
			||||||
 | 
					            for mt in MEASUREMENT_TYPES:
 | 
				
			||||||
 | 
					                suffix = '_{}'.format(mt)
 | 
				
			||||||
 | 
					                if entry.endswith(suffix):
 | 
				
			||||||
 | 
					                    site =  entry[:-len(suffix)]
 | 
				
			||||||
 | 
					                    measure = mt
 | 
				
			||||||
 | 
					                    name = '{}_{}'.format(site, measure)
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                site = entry
 | 
				
			||||||
 | 
					                measure = 'unknown'
 | 
				
			||||||
 | 
					                name = entry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            chan = InstrumentChannel(name, site, measure)
 | 
				
			||||||
 | 
					            self.channels.append(chan)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class InstrumentChannel(object):
 | 
					class InstrumentChannel(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										76
									
								
								devlib/instrument/frames.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								devlib/instrument/frames.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					from devlib.instrument import (Instrument, CONTINUOUS,
 | 
				
			||||||
 | 
					                               MeasurementsCsv, MeasurementType)
 | 
				
			||||||
 | 
					from devlib.utils.rendering import (GfxinfoFrameCollector,
 | 
				
			||||||
 | 
					                                    SurfaceFlingerFrameCollector,
 | 
				
			||||||
 | 
					                                    SurfaceFlingerFrame,
 | 
				
			||||||
 | 
					                                    read_gfxinfo_columns)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FramesInstrument(Instrument):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mode = CONTINUOUS
 | 
				
			||||||
 | 
					    collector_cls = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, target, collector_target, period=2, keep_raw=True):
 | 
				
			||||||
 | 
					        super(FramesInstrument, self).__init__(target)
 | 
				
			||||||
 | 
					        self.collector_target = collector_target
 | 
				
			||||||
 | 
					        self.period = period
 | 
				
			||||||
 | 
					        self.keep_raw = keep_raw
 | 
				
			||||||
 | 
					        self.collector = None
 | 
				
			||||||
 | 
					        self.header = None
 | 
				
			||||||
 | 
					        self._need_reset = True
 | 
				
			||||||
 | 
					        self._init_channels()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def reset(self, sites=None, kinds=None, channels=None):
 | 
				
			||||||
 | 
					        super(FramesInstrument, self).reset(sites, kinds, channels)
 | 
				
			||||||
 | 
					        self.collector = self.collector_cls(self.target, self.period,
 | 
				
			||||||
 | 
					                                            self.collector_target, self.header)
 | 
				
			||||||
 | 
					        self._need_reset = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start(self):
 | 
				
			||||||
 | 
					        if self._need_reset:
 | 
				
			||||||
 | 
					            self.reset()
 | 
				
			||||||
 | 
					        self.collector.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def stop(self):
 | 
				
			||||||
 | 
					        self.collector.stop()
 | 
				
			||||||
 | 
					        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)
 | 
				
			||||||
 | 
					        active_sites = [chan.label for chan in self.active_channels]
 | 
				
			||||||
 | 
					        self.collector.write_frames(outfile, columns=active_sites)
 | 
				
			||||||
 | 
					        return MeasurementsCsv(outfile, self.active_channels)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _init_channels(self):
 | 
				
			||||||
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GfxInfoFramesInstrument(FramesInstrument):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mode = CONTINUOUS
 | 
				
			||||||
 | 
					    collector_cls = GfxinfoFrameCollector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _init_channels(self):
 | 
				
			||||||
 | 
					        columns = read_gfxinfo_columns(self.target)
 | 
				
			||||||
 | 
					        for entry in columns:
 | 
				
			||||||
 | 
					            if entry == 'Flags':
 | 
				
			||||||
 | 
					                self.add_channel('Flags', MeasurementType('flags', 'flags'))
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.add_channel(entry, 'time_us')
 | 
				
			||||||
 | 
					        self.header = [chan.label for chan in self.channels.values()]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SurfaceFlingerFramesInstrument(FramesInstrument):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mode = CONTINUOUS
 | 
				
			||||||
 | 
					    collector_cls = SurfaceFlingerFrameCollector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _init_channels(self):
 | 
				
			||||||
 | 
					        for field in SurfaceFlingerFrame._fields:
 | 
				
			||||||
 | 
					            # remove the "_time" from filed names to avoid duplication
 | 
				
			||||||
 | 
					            self.add_channel(field[:-5], 'time_us')
 | 
				
			||||||
 | 
					        self.header = [chan.label for chan in self.channels.values()]
 | 
				
			||||||
							
								
								
									
										205
									
								
								devlib/utils/rendering.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								devlib/utils/rendering.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,205 @@
 | 
				
			|||||||
 | 
					import csv
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import shutil
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import tempfile
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					from collections import namedtuple, OrderedDict
 | 
				
			||||||
 | 
					from distutils.version import LooseVersion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from devlib.exception  import WorkerThreadError, TargetNotRespondingError, TimeoutError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logger = logging.getLogger('rendering')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SurfaceFlingerFrame = namedtuple('SurfaceFlingerFrame',
 | 
				
			||||||
 | 
					                                 'desired_present_time actual_present_time frame_ready_time')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FrameCollector(threading.Thread):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, target, period):
 | 
				
			||||||
 | 
					        super(FrameCollector, self).__init__()
 | 
				
			||||||
 | 
					        self.target = target
 | 
				
			||||||
 | 
					        self.period = period
 | 
				
			||||||
 | 
					        self.stop_signal = threading.Event()
 | 
				
			||||||
 | 
					        self.frames = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.temp_file = None
 | 
				
			||||||
 | 
					        self.refresh_period = None
 | 
				
			||||||
 | 
					        self.drop_threshold = None
 | 
				
			||||||
 | 
					        self.unresponsive_count = 0
 | 
				
			||||||
 | 
					        self.last_ready_time = None
 | 
				
			||||||
 | 
					        self.exc = None
 | 
				
			||||||
 | 
					        self.header = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run(self):
 | 
				
			||||||
 | 
					        logger.debug('Surface flinger frame data collection started.')
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.stop_signal.clear()
 | 
				
			||||||
 | 
					            fd, self.temp_file = tempfile.mkstemp()
 | 
				
			||||||
 | 
					            logger.debug('temp file: {}'.format(self.temp_file))
 | 
				
			||||||
 | 
					            wfh = os.fdopen(fd, 'wb')
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                while not self.stop_signal.is_set():
 | 
				
			||||||
 | 
					                    self.collect_frames(wfh)
 | 
				
			||||||
 | 
					                    time.sleep(self.period)
 | 
				
			||||||
 | 
					            finally:
 | 
				
			||||||
 | 
					                wfh.close()
 | 
				
			||||||
 | 
					        except (TargetNotRespondingError, TimeoutError):  # pylint: disable=W0703
 | 
				
			||||||
 | 
					            raise
 | 
				
			||||||
 | 
					        except Exception, e:  # pylint: disable=W0703
 | 
				
			||||||
 | 
					            logger.warning('Exception on collector thread: {}({})'.format(e.__class__.__name__, e))
 | 
				
			||||||
 | 
					            self.exc = WorkerThreadError(self.name, sys.exc_info())
 | 
				
			||||||
 | 
					        logger.debug('Surface flinger frame data collection stopped.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def stop(self):
 | 
				
			||||||
 | 
					        self.stop_signal.set()
 | 
				
			||||||
 | 
					        self.join()
 | 
				
			||||||
 | 
					        if self.unresponsive_count:
 | 
				
			||||||
 | 
					            message = 'FrameCollector was unrepsonsive {} times.'.format(self.unresponsive_count)
 | 
				
			||||||
 | 
					            if self.unresponsive_count > 10:
 | 
				
			||||||
 | 
					                logger.warning(message)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                logger.debug(message)
 | 
				
			||||||
 | 
					        if self.exc:
 | 
				
			||||||
 | 
					            raise self.exc  # pylint: disable=E0702
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def process_frames(self, outfile=None):
 | 
				
			||||||
 | 
					        if not self.temp_file:
 | 
				
			||||||
 | 
					            raise RuntimeError('Attempting to process frames before running the collector')
 | 
				
			||||||
 | 
					        with open(self.temp_file) as fh:
 | 
				
			||||||
 | 
					            self._process_raw_file(fh)
 | 
				
			||||||
 | 
					        if outfile:
 | 
				
			||||||
 | 
					            shutil.copy(self.temp_file, outfile)
 | 
				
			||||||
 | 
					        os.unlink(self.temp_file)
 | 
				
			||||||
 | 
					        self.temp_file = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def write_frames(self, outfile, columns=None):
 | 
				
			||||||
 | 
					        if columns is None:
 | 
				
			||||||
 | 
					            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]
 | 
				
			||||||
 | 
					            frames = [[f[i] for i in indexes] for f in self.frames]
 | 
				
			||||||
 | 
					        with open(outfile, 'w') as wfh:
 | 
				
			||||||
 | 
					            writer = csv.writer(wfh)
 | 
				
			||||||
 | 
					            if header:
 | 
				
			||||||
 | 
					                writer.writerow(header)
 | 
				
			||||||
 | 
					            writer.writerows(frames)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def collect_frames(self, wfh):
 | 
				
			||||||
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clear(self):
 | 
				
			||||||
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _process_raw_file(self, fh):
 | 
				
			||||||
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SurfaceFlingerFrameCollector(FrameCollector):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, target, period, view, header=None):
 | 
				
			||||||
 | 
					        super(SurfaceFlingerFrameCollector, self).__init__(target, period)
 | 
				
			||||||
 | 
					        self.view = view
 | 
				
			||||||
 | 
					        self.header = header or SurfaceFlingerFrame._fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def collect_frames(self, wfh):
 | 
				
			||||||
 | 
					        for activity in self.list():
 | 
				
			||||||
 | 
					            if activity == self.view:
 | 
				
			||||||
 | 
					                wfh.write(self.get_latencies(activity))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clear(self):
 | 
				
			||||||
 | 
					        self.target.execute('dumpsys SurfaceFlinger --latency-clear ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_latencies(self, activity):
 | 
				
			||||||
 | 
					        cmd = 'dumpsys SurfaceFlinger --latency "{}"'
 | 
				
			||||||
 | 
					        return self.target.execute(cmd.format(activity))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def list(self):
 | 
				
			||||||
 | 
					        return self.target.execute('dumpsys SurfaceFlinger --list').split('\r\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _process_raw_file(self, fh):
 | 
				
			||||||
 | 
					        text = fh.read().replace('\r\n', '\n').replace('\r', '\n')
 | 
				
			||||||
 | 
					        for line in text.split('\n'):
 | 
				
			||||||
 | 
					            line = line.strip()
 | 
				
			||||||
 | 
					            if line:
 | 
				
			||||||
 | 
					                self._process_trace_line(line)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _process_trace_line(self, line):
 | 
				
			||||||
 | 
					        parts = line.split()
 | 
				
			||||||
 | 
					        if len(parts) == 3:
 | 
				
			||||||
 | 
					            frame = SurfaceFlingerFrame(*map(int, parts))
 | 
				
			||||||
 | 
					            if not frame.frame_ready_time:
 | 
				
			||||||
 | 
					                return # "null" frame
 | 
				
			||||||
 | 
					            if frame.frame_ready_time <= self.last_ready_time:
 | 
				
			||||||
 | 
					                return  # duplicate frame
 | 
				
			||||||
 | 
					            if (frame.frame_ready_time - frame.desired_present_time) > self.drop_threshold:
 | 
				
			||||||
 | 
					                logger.debug('Dropping bogus frame {}.'.format(line))
 | 
				
			||||||
 | 
					                return  # bogus data
 | 
				
			||||||
 | 
					            self.last_ready_time = frame.frame_ready_time
 | 
				
			||||||
 | 
					            self.frames.append(frame)
 | 
				
			||||||
 | 
					        elif len(parts) == 1:
 | 
				
			||||||
 | 
					            self.refresh_period = int(parts[0])
 | 
				
			||||||
 | 
					            self.drop_threshold = self.refresh_period * 1000
 | 
				
			||||||
 | 
					        elif 'SurfaceFlinger appears to be unresponsive, dumping anyways' in line:
 | 
				
			||||||
 | 
					            self.unresponsive_count += 1
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.warning('Unexpected SurfaceFlinger dump output: {}'.format(line))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def read_gfxinfo_columns(target):
 | 
				
			||||||
 | 
					    output = target.execute('dumpsys gfxinfo --list framestats')
 | 
				
			||||||
 | 
					    lines = iter(output.split('\n'))
 | 
				
			||||||
 | 
					    for line in lines:
 | 
				
			||||||
 | 
					        if line.startswith('---PROFILEDATA---'):
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					    columns_line = lines.next()
 | 
				
			||||||
 | 
					    return columns_line.split(',')[:-1]  # has a trailing ','
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GfxinfoFrameCollector(FrameCollector):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, target, period, package, header=None):
 | 
				
			||||||
 | 
					        super(GfxinfoFrameCollector, self).__init__(target, period)
 | 
				
			||||||
 | 
					        self.package = package
 | 
				
			||||||
 | 
					        self.header =  None
 | 
				
			||||||
 | 
					        self._init_header(header)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def collect_frames(self, wfh):
 | 
				
			||||||
 | 
					        cmd = 'dumpsys gfxinfo {} framestats'
 | 
				
			||||||
 | 
					        wfh.write(self.target.execute(cmd.format(self.package)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clear(self):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _init_header(self, header):
 | 
				
			||||||
 | 
					        if header is not None:
 | 
				
			||||||
 | 
					            self.header = header
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.header = read_gfxinfo_columns(self.target)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _process_raw_file(self, fh):
 | 
				
			||||||
 | 
					        found = False
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            while True:
 | 
				
			||||||
 | 
					                for line in fh:
 | 
				
			||||||
 | 
					                    if line.startswith('---PROFILEDATA---'):
 | 
				
			||||||
 | 
					                        found = True
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                fh.next()  # headers
 | 
				
			||||||
 | 
					                for line in fh:
 | 
				
			||||||
 | 
					                    if line.startswith('---PROFILEDATA---'):
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					                    self.frames.append(map(int, line.strip().split(',')[:-1]))  # has a trailing ','
 | 
				
			||||||
 | 
					        except StopIteration:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        if not found:
 | 
				
			||||||
 | 
					            logger.warning('Could not find frames data in gfxinfo output')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
		Reference in New Issue
	
	Block a user