Sergei Trofimov | f692315 | 2017-09-07 17:36:18 +0100 | [diff] [blame^] | 1 | from __future__ import division |
| 2 | import csv |
| 3 | import os |
| 4 | import re |
| 5 | |
| 6 | try: |
| 7 | import pandas as pd |
| 8 | except ImportError: |
| 9 | pd = None |
| 10 | |
| 11 | from devlib import DerivedMeasurements, DerivedMetric, MeasurementsCsv, InstrumentChannel |
| 12 | from devlib.utils.rendering import gfxinfo_get_last_dump, VSYNC_INTERVAL |
| 13 | from devlib.utils.types import numeric |
| 14 | |
| 15 | |
| 16 | class DerivedFpsStats(DerivedMeasurements): |
| 17 | |
| 18 | def __init__(self, drop_threshold=5, suffix=None, filename=None, outdir=None): |
| 19 | self.drop_threshold = drop_threshold |
| 20 | self.suffix = suffix |
| 21 | self.filename = filename |
| 22 | self.outdir = outdir |
| 23 | if (filename is None) and (suffix is None): |
| 24 | self.suffix = '-fps' |
| 25 | elif (filename is not None) and (suffix is not None): |
| 26 | raise ValueError('suffix and filename cannot be specified at the same time.') |
| 27 | if filename is not None and os.sep in filename: |
| 28 | raise ValueError('filename cannot be a path (cannot countain "{}"'.format(os.sep)) |
| 29 | |
| 30 | def process(self, measurements_csv): |
| 31 | if isinstance(measurements_csv, basestring): |
| 32 | measurements_csv = MeasurementsCsv(measurements_csv) |
| 33 | if pd is not None: |
| 34 | return self._process_with_pandas(measurements_csv) |
| 35 | return self._process_without_pandas(measurements_csv) |
| 36 | |
| 37 | def _get_csv_file_name(self, frames_file): |
| 38 | outdir = self.outdir or os.path.dirname(frames_file) |
| 39 | if self.filename: |
| 40 | return os.path.join(outdir, self.filename) |
| 41 | |
| 42 | frames_basename = os.path.basename(frames_file) |
| 43 | rest, ext = os.path.splitext(frames_basename) |
| 44 | csv_basename = rest + self.suffix + ext |
| 45 | return os.path.join(outdir, csv_basename) |
| 46 | |
| 47 | |
| 48 | class DerivedGfxInfoStats(DerivedFpsStats): |
| 49 | |
| 50 | @staticmethod |
| 51 | def process_raw(filepath, *args): |
| 52 | metrics = [] |
| 53 | dump = gfxinfo_get_last_dump(filepath) |
| 54 | seen_stats = False |
| 55 | for line in dump.split('\n'): |
| 56 | if seen_stats and not line.strip(): |
| 57 | break |
| 58 | elif line.startswith('Janky frames:'): |
| 59 | text = line.split(': ')[-1] |
| 60 | val_text, pc_text = text.split('(') |
| 61 | metrics.append(DerivedMetric('janks', numeric(val_text.strip()), 'count')) |
| 62 | metrics.append(DerivedMetric('janks_pc', numeric(pc_text[:-3]), 'percent')) |
| 63 | elif ' percentile: ' in line: |
| 64 | ptile, val_text = line.split(' percentile: ') |
| 65 | name = 'render_time_{}_ptile'.format(ptile) |
| 66 | value = numeric(val_text.strip()[:-2]) |
| 67 | metrics.append(DerivedMetric(name, value, 'time_ms')) |
| 68 | elif line.startswith('Number '): |
| 69 | name_text, val_text = line.strip().split(': ') |
| 70 | name = name_text[7:].lower().replace(' ', '_') |
| 71 | value = numeric(val_text) |
| 72 | metrics.append(DerivedMetric(name, value, 'count')) |
| 73 | else: |
| 74 | continue |
| 75 | seen_stats = True |
| 76 | return metrics |
| 77 | |
| 78 | def _process_without_pandas(self, measurements_csv): |
| 79 | per_frame_fps = [] |
| 80 | start_vsync, end_vsync = None, None |
| 81 | frame_count = 0 |
| 82 | |
| 83 | for frame_data in measurements_csv.iter_values(): |
| 84 | if frame_data.Flags_flags != 0: |
| 85 | continue |
| 86 | frame_count += 1 |
| 87 | |
| 88 | if start_vsync is None: |
| 89 | start_vsync = frame_data.Vsync_time_us |
| 90 | end_vsync = frame_data.Vsync_time_us |
| 91 | |
| 92 | frame_time = frame_data.FrameCompleted_time_us - frame_data.IntendedVsync_time_us |
| 93 | pff = 1e9 / frame_time |
| 94 | if pff > self.drop_threshold: |
| 95 | per_frame_fps.append([pff]) |
| 96 | |
| 97 | if frame_count: |
| 98 | duration = end_vsync - start_vsync |
| 99 | fps = (1e9 * frame_count) / float(duration) |
| 100 | else: |
| 101 | duration = 0 |
| 102 | fps = 0 |
| 103 | |
| 104 | csv_file = self._get_csv_file_name(measurements_csv.path) |
| 105 | with open(csv_file, 'wb') as wfh: |
| 106 | writer = csv.writer(wfh) |
| 107 | writer.writerow(['fps']) |
| 108 | writer.writerows(per_frame_fps) |
| 109 | |
| 110 | return [DerivedMetric('fps', fps, 'fps'), |
| 111 | DerivedMetric('total_frames', frame_count, 'frames'), |
| 112 | MeasurementsCsv(csv_file)] |
| 113 | |
| 114 | def _process_with_pandas(self, measurements_csv): |
| 115 | data = pd.read_csv(measurements_csv.path) |
| 116 | data = data[data.Flags_flags == 0] |
| 117 | frame_time = data.FrameCompleted_time_us - data.IntendedVsync_time_us |
| 118 | per_frame_fps = (1e9 / frame_time) |
| 119 | keep_filter = per_frame_fps > self.drop_threshold |
| 120 | per_frame_fps = per_frame_fps[keep_filter] |
| 121 | per_frame_fps.name = 'fps' |
| 122 | |
| 123 | frame_count = data.index.size |
| 124 | if frame_count > 1: |
| 125 | duration = data.Vsync_time_us.iloc[-1] - data.Vsync_time_us.iloc[0] |
| 126 | fps = (1e9 * frame_count) / float(duration) |
| 127 | else: |
| 128 | duration = 0 |
| 129 | fps = 0 |
| 130 | |
| 131 | csv_file = self._get_csv_file_name(measurements_csv.path) |
| 132 | per_frame_fps.to_csv(csv_file, index=False, header=True) |
| 133 | |
| 134 | return [DerivedMetric('fps', fps, 'fps'), |
| 135 | DerivedMetric('total_frames', frame_count, 'frames'), |
| 136 | MeasurementsCsv(csv_file)] |
| 137 | |