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 {Trace} from '../../public/trace'; 16import {PerfettoPlugin} from '../../public/plugin'; 17import {METRIC_HANDLERS} from './handlers/handlerRegistry'; 18import {MetricData, MetricHandlerMatch} from './handlers/metricUtils'; 19import {PLUGIN_ID} from './pluginId'; 20import AndroidCujsPlugin from '../dev.perfetto.AndroidCujs'; 21 22const JANK_CUJ_QUERY_PRECONDITIONS = ` 23 SELECT RUN_METRIC('android/android_blocking_calls_cuj_metric.sql'); 24`; 25 26function getMetricsFromHash(): string[] { 27 const metricVal = location.hash; 28 const regex = new RegExp(`${PLUGIN_ID}:metrics=(.*)`); 29 const match = metricVal.match(regex); 30 if (match === null) { 31 return []; 32 } 33 const capturedString = match[1]; 34 let metricList: string[] = []; 35 if (capturedString.includes('--')) { 36 metricList = capturedString.split('--'); 37 } else { 38 metricList = [capturedString]; 39 } 40 return metricList.map((metric) => decodeURIComponent(metric)); 41} 42 43let metrics: string[]; 44 45/** 46 * Plugin that adds and pins the debug track for the metric passed 47 * For more context - 48 * This plugin reads the names of regressed metrics from the url upon loading 49 * It then checks the metric names against some handlers and if they 50 * match it accordingly adds the debug tracks for them 51 * This way when comparing two different perfetto traces before and after 52 * the regression, the user will not have to manually search for the 53 * slices related to the regressed metric 54 */ 55export default class implements PerfettoPlugin { 56 static readonly id = PLUGIN_ID; 57 static readonly dependencies = [AndroidCujsPlugin]; 58 59 static onActivate(): void { 60 metrics = getMetricsFromHash(); 61 } 62 63 async onTraceLoad(ctx: Trace) { 64 ctx.commands.registerCommand({ 65 id: 'dev.perfetto.PinAndroidPerfMetrics#PinAndroidPerfMetrics', 66 name: 'Add and Pin: Jank Metric Slice', 67 callback: async (metric) => { 68 metric = prompt('Metrics names (separated by comma)', ''); 69 if (metric === null) return; 70 const metricList = metric.split(','); 71 this.callHandlers(metricList, ctx); 72 }, 73 }); 74 if (metrics.length !== 0) { 75 this.callHandlers(metrics, ctx); 76 } 77 } 78 79 private async callHandlers(metricsList: string[], ctx: Trace) { 80 // List of metrics that actually match some handler 81 const metricsToShow: MetricHandlerMatch[] = 82 this.getMetricsToShow(metricsList); 83 84 if (metricsToShow.length === 0) { 85 return; 86 } 87 88 await ctx.engine.query(JANK_CUJ_QUERY_PRECONDITIONS); 89 for (const {metricData, metricHandler} of metricsToShow) { 90 metricHandler.addMetricTrack(metricData, ctx); 91 } 92 } 93 94 private getMetricsToShow(metricList: string[]): MetricHandlerMatch[] { 95 const sortedMetricList = [...metricList].sort(); 96 const validMetrics: MetricHandlerMatch[] = []; 97 const alreadyMatchedMetricData: Set<string> = new Set(); 98 for (const metric of sortedMetricList) { 99 for (const metricHandler of METRIC_HANDLERS) { 100 const metricData = metricHandler.match(metric); 101 if (!metricData) continue; 102 const jsonMetricData = this.metricDataToJson(metricData); 103 if (!alreadyMatchedMetricData.has(jsonMetricData)) { 104 alreadyMatchedMetricData.add(jsonMetricData); 105 validMetrics.push({ 106 metricData: metricData, 107 metricHandler: metricHandler, 108 }); 109 } 110 } 111 } 112 return validMetrics; 113 } 114 115 private metricDataToJson(metricData: MetricData): string { 116 // Used to have a deterministic keys order. 117 return JSON.stringify(metricData, Object.keys(metricData).sort()); 118 } 119} 120