xref: /aosp_15_r20/external/perfetto/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/index.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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