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 BlockingCallMetricData, 18 MetricHandler, 19} from './metricUtils'; 20import {Trace} from '../../../public/trace'; 21import {addJankCUJDebugTrack, addLatencyCUJDebugTrack} from '../../dev.perfetto.AndroidCujs'; 22import {addDebugSliceTrack} from '../../../components/tracks/debug_tracks'; 23 24class BlockingCallMetricHandler implements MetricHandler { 25 /** 26 * Matches metric key for blocking call and per-frame blocking call metrics & return parsed data 27 * if successful. 28 * 29 * @param {string} metricKey The metric key to match. 30 * @returns {BlockingCallMetricData | undefined} Parsed data or undefined if no match. 31 */ 32 public match(metricKey: string): BlockingCallMetricData | undefined { 33 const matcher = 34 /perfetto_android_blocking_call(?:_per_frame)?-cuj-name-(?<process>.*)-name-(?<cujName>.*)-blocking_calls-name-(?<blockingCallName>([^\-]*))-(?<aggregation>.*)/; 35 const match = matcher.exec(metricKey); 36 if (!match?.groups) { 37 return undefined; 38 } 39 const metricData: BlockingCallMetricData = { 40 process: expandProcessName(match.groups.process), 41 cujName: match.groups.cujName, 42 blockingCallName: match.groups.blockingCallName, 43 aggregation: match.groups.aggregation, 44 }; 45 return metricData; 46 } 47 48 /** 49 * Adds the debug tracks for Blocking Call metrics 50 * 51 * @param {BlockingCallMetricData} metricData Parsed metric data for the cuj scoped jank 52 * @param {Trace} ctx PluginContextTrace for trace related properties and methods 53 * @returns {void} Adds one track for Jank CUJ slice and one for Janky CUJ frames 54 */ 55 public addMetricTrack(metricData: BlockingCallMetricData, ctx: Trace): void { 56 this.pinSingleCuj(ctx, metricData); 57 const config = this.blockingCallTrackConfig(metricData); 58 addDebugSliceTrack({trace: ctx, ...config}); 59 } 60 61 private async pinSingleCuj(ctx: Trace, metricData: BlockingCallMetricData) { 62 const jankTrackName = `Jank CUJ: ${metricData.cujName}`; 63 const latencyTrackName = `Latency CUJ: ${metricData.cujName}`; 64 // TODO: b/296349525 - Refactor once CUJ tables are migrated to stdlib 65 // Currently, we try to pin a Jank CUJ track and if that fails we add 66 // a Latency CUJ track. We can instead look up a single CUJ table to 67 // better determine what to query and pin. 68 const jankCujPinned = await addJankCUJDebugTrack(ctx, jankTrackName, metricData.cujName); 69 if (!jankCujPinned) { 70 addLatencyCUJDebugTrack(ctx, latencyTrackName, metricData.cujName); 71 } 72 } 73 74 private blockingCallTrackConfig(metricData: BlockingCallMetricData) { 75 const cuj = metricData.cujName; 76 const processName = metricData.process; 77 const blockingCallName = metricData.blockingCallName; 78 79 // TODO: b/296349525 - Migrate jank tables from run metrics to stdlib 80 const blockingCallDuringCujQuery = ` 81 SELECT name, ts, dur 82 FROM main_thread_slices_scoped_to_cujs 83 WHERE process_name = "${processName}" 84 AND cuj_name = "${cuj}" 85 AND name = "${blockingCallName}" 86 `; 87 88 const trackName = 'Blocking calls in ' + processName; 89 return { 90 data: { 91 sqlSource: blockingCallDuringCujQuery, 92 columns: ['name', 'ts', 'dur'], 93 }, 94 columns: {ts: 'ts', dur: 'dur', name: 'name'}, 95 argColumns: ['name', 'ts', 'dur'], 96 trackName, 97 }; 98 } 99} 100 101export const pinBlockingCallHandlerInstance = new BlockingCallMetricHandler(); 102