1// Copyright (C) 2024 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import { 16 expandProcessName, 17 CujScopedMetricData, 18 MetricHandler, 19 JankType, 20} from './metricUtils'; 21import {NUM} from '../../../trace_processor/query_result'; 22import {Trace} from '../../../public/trace'; 23 24// TODO(primiano): make deps check stricter, we shouldn't allow plugins to 25// depend on each other. 26import {focusOnSlice} from '../../dev.perfetto.AndroidCujs/trackUtils'; 27import {addDebugSliceTrack} from '../../../components/tracks/debug_tracks'; 28 29const ENABLE_FOCUS_ON_FIRST_JANK = true; 30 31class PinCujScopedJank implements MetricHandler { 32 /** 33 * Matches metric key & return parsed data if successful. 34 * 35 * @param {string} metricKey The metric key to match. 36 * @returns {CujScopedMetricData | undefined} Parsed data or undefined if no match. 37 */ 38 public match(metricKey: string): CujScopedMetricData | undefined { 39 const matcher = 40 /perfetto_cuj_(?<process>.*)-(?<cujName>.*)-.*-missed_(?<jankType>frames|sf_frames|app_frames)/; 41 const match = matcher.exec(metricKey); 42 if (!match?.groups) { 43 return undefined; 44 } 45 const metricData: CujScopedMetricData = { 46 process: expandProcessName(match.groups.process), 47 cujName: match.groups.cujName, 48 jankType: match.groups.jankType as JankType, 49 }; 50 return metricData; 51 1; 52 } 53 54 /** 55 * Adds the debug tracks for cuj Scoped jank metrics. 56 * 57 * @param {CujScopedMetricData} metricData Parsed metric data for the cuj scoped jank 58 * @param {Trace} ctx PluginContextTrace for trace related properties and methods 59 * @returns {void} Adds one track for Jank CUJ slice and one for Janky CUJ frames 60 */ 61 public async addMetricTrack(metricData: CujScopedMetricData, ctx: Trace) { 62 // TODO: b/349502258 - Refactor to single API 63 const {tableName, ...config} = await this.cujScopedTrackConfig( 64 metricData, 65 ctx, 66 ); 67 addDebugSliceTrack({trace: ctx, ...config}); 68 if (ENABLE_FOCUS_ON_FIRST_JANK) { 69 await this.focusOnFirstJank(ctx, tableName); 70 } 71 } 72 73 private async cujScopedTrackConfig( 74 metricData: CujScopedMetricData, 75 ctx: Trace, 76 ) { 77 let jankTypeFilter; 78 let jankTypeDisplayName = 'all'; 79 if (metricData.jankType?.includes('app')) { 80 jankTypeFilter = ' AND app_missed > 0'; 81 jankTypeDisplayName = 'app'; 82 } else if (metricData.jankType?.includes('sf')) { 83 jankTypeFilter = ' AND sf_missed > 0'; 84 jankTypeDisplayName = 'sf'; 85 } 86 const cuj = metricData.cujName; 87 const processName = metricData.process; 88 89 const tableWithJankyFramesName = `_janky_frames_during_cuj_from_metric_key_${Math.floor(Math.random() * 1_000_000)}`; 90 91 const createJankyCujFrameTable = ` 92 CREATE OR REPLACE PERFETTO TABLE ${tableWithJankyFramesName} AS 93 SELECT 94 f.vsync as id, 95 f.ts AS ts, 96 f.dur as dur 97 FROM android_jank_cuj_frame f LEFT JOIN android_jank_cuj cuj USING (cuj_id) 98 WHERE cuj.process_name = "${processName}" 99 AND cuj_name = "${cuj}" ${jankTypeFilter} 100 `; 101 102 await ctx.engine.query(createJankyCujFrameTable); 103 104 const jankyFramesDuringCujQuery = ` 105 SELECT id, ts, dur 106 FROM ${tableWithJankyFramesName} 107 `; 108 109 const trackName = jankTypeDisplayName + ' missed frames in ' + processName; 110 111 const cujScopedJankSlice = { 112 data: { 113 sqlSource: jankyFramesDuringCujQuery, 114 columns: ['id', 'ts', 'dur'], 115 }, 116 columns: {ts: 'ts', dur: 'dur', name: 'id'}, 117 argColumns: ['id', 'ts', 'dur'], 118 trackName, 119 }; 120 121 return { 122 ...cujScopedJankSlice, 123 tableName: tableWithJankyFramesName, 124 }; 125 } 126 127 private async focusOnFirstJank(ctx: Trace, tableWithJankyFramesName: string) { 128 const queryForFirstJankyFrame = ` 129 SELECT id as slice_id, track_id 130 FROM actual_frame_timeline_slice 131 WHERE name = cast_string!( 132 (SELECT id FROM ${tableWithJankyFramesName} LIMIT 1) 133 ); 134 `; 135 const queryResult = await ctx.engine.query(queryForFirstJankyFrame); 136 if (queryResult.numRows() === 0) { 137 return; 138 } 139 const row = queryResult.firstRow({ 140 slice_id: NUM, 141 track_id: NUM, 142 }); 143 focusOnSlice(ctx, row.slice_id); 144 } 145} 146 147export const pinCujScopedJankInstance = new PinCujScopedJank(); 148