| from __future__ import division |
| import csv |
| import os |
| import re |
| |
| try: |
| import pandas as pd |
| except ImportError: |
| pd = None |
| |
| from devlib import DerivedMeasurements, DerivedMetric, MeasurementsCsv, InstrumentChannel |
| from devlib.utils.rendering import gfxinfo_get_last_dump, VSYNC_INTERVAL |
| from devlib.utils.types import numeric |
| |
| |
| class DerivedFpsStats(DerivedMeasurements): |
| |
| def __init__(self, drop_threshold=5, suffix=None, filename=None, outdir=None): |
| self.drop_threshold = drop_threshold |
| self.suffix = suffix |
| self.filename = filename |
| self.outdir = outdir |
| if (filename is None) and (suffix is None): |
| self.suffix = '-fps' |
| elif (filename is not None) and (suffix is not None): |
| raise ValueError('suffix and filename cannot be specified at the same time.') |
| if filename is not None and os.sep in filename: |
| raise ValueError('filename cannot be a path (cannot countain "{}"'.format(os.sep)) |
| |
| def process(self, measurements_csv): |
| if isinstance(measurements_csv, basestring): |
| measurements_csv = MeasurementsCsv(measurements_csv) |
| if pd is not None: |
| return self._process_with_pandas(measurements_csv) |
| return self._process_without_pandas(measurements_csv) |
| |
| def _get_csv_file_name(self, frames_file): |
| outdir = self.outdir or os.path.dirname(frames_file) |
| if self.filename: |
| return os.path.join(outdir, self.filename) |
| |
| frames_basename = os.path.basename(frames_file) |
| rest, ext = os.path.splitext(frames_basename) |
| csv_basename = rest + self.suffix + ext |
| return os.path.join(outdir, csv_basename) |
| |
| |
| class DerivedGfxInfoStats(DerivedFpsStats): |
| |
| @staticmethod |
| def process_raw(filepath, *args): |
| metrics = [] |
| dump = gfxinfo_get_last_dump(filepath) |
| seen_stats = False |
| for line in dump.split('\n'): |
| if seen_stats and not line.strip(): |
| break |
| elif line.startswith('Janky frames:'): |
| text = line.split(': ')[-1] |
| val_text, pc_text = text.split('(') |
| metrics.append(DerivedMetric('janks', numeric(val_text.strip()), 'count')) |
| metrics.append(DerivedMetric('janks_pc', numeric(pc_text[:-3]), 'percent')) |
| elif ' percentile: ' in line: |
| ptile, val_text = line.split(' percentile: ') |
| name = 'render_time_{}_ptile'.format(ptile) |
| value = numeric(val_text.strip()[:-2]) |
| metrics.append(DerivedMetric(name, value, 'time_ms')) |
| elif line.startswith('Number '): |
| name_text, val_text = line.strip().split(': ') |
| name = name_text[7:].lower().replace(' ', '_') |
| value = numeric(val_text) |
| metrics.append(DerivedMetric(name, value, 'count')) |
| else: |
| continue |
| seen_stats = True |
| return metrics |
| |
| def _process_without_pandas(self, measurements_csv): |
| per_frame_fps = [] |
| start_vsync, end_vsync = None, None |
| frame_count = 0 |
| |
| for frame_data in measurements_csv.iter_values(): |
| if frame_data.Flags_flags != 0: |
| continue |
| frame_count += 1 |
| |
| if start_vsync is None: |
| start_vsync = frame_data.Vsync_time_us |
| end_vsync = frame_data.Vsync_time_us |
| |
| frame_time = frame_data.FrameCompleted_time_us - frame_data.IntendedVsync_time_us |
| pff = 1e9 / frame_time |
| if pff > self.drop_threshold: |
| per_frame_fps.append([pff]) |
| |
| if frame_count: |
| duration = end_vsync - start_vsync |
| fps = (1e9 * frame_count) / float(duration) |
| else: |
| duration = 0 |
| fps = 0 |
| |
| csv_file = self._get_csv_file_name(measurements_csv.path) |
| with open(csv_file, 'wb') as wfh: |
| writer = csv.writer(wfh) |
| writer.writerow(['fps']) |
| writer.writerows(per_frame_fps) |
| |
| return [DerivedMetric('fps', fps, 'fps'), |
| DerivedMetric('total_frames', frame_count, 'frames'), |
| MeasurementsCsv(csv_file)] |
| |
| def _process_with_pandas(self, measurements_csv): |
| data = pd.read_csv(measurements_csv.path) |
| data = data[data.Flags_flags == 0] |
| frame_time = data.FrameCompleted_time_us - data.IntendedVsync_time_us |
| per_frame_fps = (1e9 / frame_time) |
| keep_filter = per_frame_fps > self.drop_threshold |
| per_frame_fps = per_frame_fps[keep_filter] |
| per_frame_fps.name = 'fps' |
| |
| frame_count = data.index.size |
| if frame_count > 1: |
| duration = data.Vsync_time_us.iloc[-1] - data.Vsync_time_us.iloc[0] |
| fps = (1e9 * frame_count) / float(duration) |
| else: |
| duration = 0 |
| fps = 0 |
| |
| csv_file = self._get_csv_file_name(measurements_csv.path) |
| per_frame_fps.to_csv(csv_file, index=False, header=True) |
| |
| return [DerivedMetric('fps', fps, 'fps'), |
| DerivedMetric('total_frames', frame_count, 'frames'), |
| MeasurementsCsv(csv_file)] |
| |