rendering: Add surfaceflinger frame collector
Add a FrameCollector implementation that utilizes the output from
surfaceflinger (the Android compositor). This is is the only method for
accessing rendering data on older Android devices.
diff --git a/devlib/utils/rendering.py b/devlib/utils/rendering.py
index 53dd238..3b7b6c4 100644
--- a/devlib/utils/rendering.py
+++ b/devlib/utils/rendering.py
@@ -15,6 +15,9 @@
logger = logging.getLogger('rendering')
+SurfaceFlingerFrame = namedtuple('SurfaceFlingerFrame',
+ 'desired_present_time actual_present_time frame_ready_time')
+
class FrameCollector(threading.Thread):
@@ -99,6 +102,57 @@
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'))