| // Copyright (C) 2024 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import {Trace} from '../../public/trace'; |
| import {PerfettoPlugin} from '../../public/plugin'; |
| import {METRIC_HANDLERS} from './handlers/handlerRegistry'; |
| import {MetricData, MetricHandlerMatch} from './handlers/metricUtils'; |
| import {PLUGIN_ID} from './pluginId'; |
| |
| const JANK_CUJ_QUERY_PRECONDITIONS = ` |
| SELECT RUN_METRIC('android/android_blocking_calls_cuj_metric.sql'); |
| `; |
| |
| function getMetricsFromHash(): string[] { |
| const metricVal = location.hash; |
| const regex = new RegExp(`${PLUGIN_ID}:metrics=(.*)`); |
| const match = metricVal.match(regex); |
| if (match === null) { |
| return []; |
| } |
| const capturedString = match[1]; |
| let metricList: string[] = []; |
| if (capturedString.includes('--')) { |
| metricList = capturedString.split('--'); |
| } else { |
| metricList = [capturedString]; |
| } |
| return metricList.map((metric) => decodeURIComponent(metric)); |
| } |
| |
| let metrics: string[]; |
| |
| /** |
| * Plugin that adds and pins the debug track for the metric passed |
| * For more context - |
| * This plugin reads the names of regressed metrics from the url upon loading |
| * It then checks the metric names against some handlers and if they |
| * match it accordingly adds the debug tracks for them |
| * This way when comparing two different perfetto traces before and after |
| * the regression, the user will not have to manually search for the |
| * slices related to the regressed metric |
| */ |
| export default class implements PerfettoPlugin { |
| static readonly id = PLUGIN_ID; |
| |
| static onActivate(): void { |
| metrics = getMetricsFromHash(); |
| } |
| |
| async onTraceLoad(ctx: Trace) { |
| ctx.addEventListener('traceready', () => { |
| ctx.commands.registerCommand({ |
| id: 'dev.perfetto.PinAndroidPerfMetrics#PinAndroidPerfMetrics', |
| name: 'Add and Pin: Jank Metric Slice', |
| callback: async (metric) => { |
| metric = prompt('Metrics names (separated by comma)', ''); |
| if (metric === null) return; |
| const metricList = metric.split(','); |
| this.callHandlers(metricList, ctx); |
| }, |
| }); |
| if (metrics.length !== 0) { |
| this.callHandlers(metrics, ctx); |
| } |
| }); |
| } |
| |
| private async callHandlers(metricsList: string[], ctx: Trace) { |
| // List of metrics that actually match some handler |
| const metricsToShow: MetricHandlerMatch[] = |
| this.getMetricsToShow(metricsList); |
| |
| if (metricsToShow.length === 0) { |
| return; |
| } |
| |
| await ctx.engine.query(JANK_CUJ_QUERY_PRECONDITIONS); |
| for (const {metricData, metricHandler} of metricsToShow) { |
| metricHandler.addMetricTrack(metricData, ctx); |
| } |
| } |
| |
| private getMetricsToShow(metricList: string[]): MetricHandlerMatch[] { |
| const sortedMetricList = [...metricList].sort(); |
| const validMetrics: MetricHandlerMatch[] = []; |
| const alreadyMatchedMetricData: Set<string> = new Set(); |
| for (const metric of sortedMetricList) { |
| for (const metricHandler of METRIC_HANDLERS) { |
| const metricData = metricHandler.match(metric); |
| if (!metricData) continue; |
| const jsonMetricData = this.metricDataToJson(metricData); |
| if (!alreadyMatchedMetricData.has(jsonMetricData)) { |
| alreadyMatchedMetricData.add(jsonMetricData); |
| validMetrics.push({ |
| metricData: metricData, |
| metricHandler: metricHandler, |
| }); |
| } |
| } |
| } |
| return validMetrics; |
| } |
| |
| private metricDataToJson(metricData: MetricData): string { |
| // Used to have a deterministic keys order. |
| return JSON.stringify(metricData, Object.keys(metricData).sort()); |
| } |
| } |