diff --git a/wp/constants.py b/wp/constants.py index d9a044d..7e97db7 100644 --- a/wp/constants.py +++ b/wp/constants.py @@ -5,8 +5,8 @@ FULL_METRICS = [ 'power', 'idle', 'idle-miss', 'freq', 'overutil', 'pelt', 'capacity', - 'uclamp', 'adpf', 'thermal', 'perf-trace-event', 'wakeup-latency', - 'tasks-residency', 'tasks-activations', + 'uclamp', 'adpf', 'thermal', 'fps', 'wakeup-latency', + 'tasks-residency', 'tasks-activations', 'perf-trace-event', 'cgroup-attach', 'wakeup-latency-cgroup', 'tasks-residency-cgroup', 'energy-estimate', ] diff --git a/wp/notebook.py b/wp/notebook.py index 42afc83..42915f7 100644 --- a/wp/notebook.py +++ b/wp/notebook.py @@ -259,6 +259,7 @@ def analysis_to_loader(self, analysis: str) -> Callable: 'task_activations_stats_cluster': self._load_task_activations_stats, 'task_activations_stats_cluster_melt': self._load_task_activations_stats, 'uclamp_updates': self._load_uclamp_updates, + 'fps': self._load_fps, } return mapping[analysis] @@ -1592,6 +1593,44 @@ def uclamp_per_task_line(self, height=600, width=1600, include_label=True, self.ana.hv_figures[self.ana._title_to_filename(title, '__line')] = layout return layout + # --- FPS instrument --- + + def _load_fps(self): + self.ana.load_combined_analysis('fps.pqt', allow_missing=True) + log.info('Loaded fps into analysis') + + @requires_analysis(['fps']) + def frame_rendering_line(self, height: int = 600, width: int = 1600, include_label: bool = True, + target_fps: int = 30, title: str = 'Frame rendering times over time'): + """ + Plot the frame rendering times over time overlaid between different runs, with a toggle to select the iteration. + + :param target_fps: Target fps to draw the target rendering line from + + .. note:: This plot uses HoloViews. The resulting figure will be saved to `WorkloadNotebookAnalysis.hv_figures`. + """ + if include_label: + title = f"{self.ana.label} - {title}" + + df = self.ana.analysis['fps'] + df['target_render_time'] = 1000.0 / target_fps + + ds = hv.Dataset( + df, + ['ts_iter', hv.Dimension('tag', values=self.ana.tags), 'iteration'], + ['render_time'] + ) + + layout = ds.to(hv.Curve, 'ts_iter', 'render_time').overlay('tag').opts( + legend_position='bottom', title=title, framewise=True + ) + layout *= hv.Curve(df, 'ts_iter', 'target_render_time').opts(color='orange') + layout.opts( + opts.Curve(height=height, width=width, interpolation='steps-pre', framewise=True), + ) + + return layout + # -------- helper functions -------- def gmean_bars(self, df: pd.DataFrame, x: str = 'stat', y: str = 'value', color: str = 'tag', diff --git a/wp/processor.py b/wp/processor.py index c07262c..b09d474 100644 --- a/wp/processor.py +++ b/wp/processor.py @@ -385,3 +385,31 @@ def capacity_analysis(self): df.write_parquet(os.path.join(self.analysis_path, 'capacity.pqt')) print(df) + + def fps_analysis(self): + log.info('Collecting fps data') + + def process_fps_df(df, iteration): + return df.filter( + pl.col('actual_present_time_us') != 0x7fffffffffffffff + ).with_columns( + pl.col('*') / 1000000.0 + ).select( + pl.col('actual_present_time_us').alias('ts') / 1000.0, + (pl.col('actual_present_time_us') - pl.col('actual_present_time_us').first()).alias('ts_iter') / 1000.0, + pl.col('desired_present_time_us').alias('desired_ts') / 1000.0, + pl.col('frame_ready_time_us').alias('ready_ts') / 1000.0, + pl.col('actual_present_time_us').diff().alias('render_time'), + pl.col('frame_ready_time_us').diff().alias('render_ready_time'), + pl.lit(iteration).alias('iteration'), + )[1:] + + fps = df_add_wa_output_tags(pl.concat([ + process_fps_df( + pl.read_csv(job.get_artifact_path('frames')), job.iteration + ) + for job in self.wa_output.jobs + ]), self.wa_output).sort('iteration') + + fps.write_parquet(os.path.join(self.analysis_path, 'fps.pqt')) + print(fps)