xref: /aosp_15_r20/external/perfetto/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/index.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2024 The Android Open Source Project
2*6dbdd20aSAndroid Build Coastguard Worker//
3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*6dbdd20aSAndroid Build Coastguard Worker//
7*6dbdd20aSAndroid Build Coastguard Worker//      http://www.apache.org/licenses/LICENSE-2.0
8*6dbdd20aSAndroid Build Coastguard Worker//
9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License.
14*6dbdd20aSAndroid Build Coastguard Worker
15*6dbdd20aSAndroid Build Coastguard Workerimport {Trace} from '../../public/trace';
16*6dbdd20aSAndroid Build Coastguard Workerimport {PerfettoPlugin} from '../../public/plugin';
17*6dbdd20aSAndroid Build Coastguard Workerimport {METRIC_HANDLERS} from './handlers/handlerRegistry';
18*6dbdd20aSAndroid Build Coastguard Workerimport {MetricData, MetricHandlerMatch} from './handlers/metricUtils';
19*6dbdd20aSAndroid Build Coastguard Workerimport {PLUGIN_ID} from './pluginId';
20*6dbdd20aSAndroid Build Coastguard Workerimport AndroidCujsPlugin from '../dev.perfetto.AndroidCujs';
21*6dbdd20aSAndroid Build Coastguard Worker
22*6dbdd20aSAndroid Build Coastguard Workerconst JANK_CUJ_QUERY_PRECONDITIONS = `
23*6dbdd20aSAndroid Build Coastguard Worker  SELECT RUN_METRIC('android/android_blocking_calls_cuj_metric.sql');
24*6dbdd20aSAndroid Build Coastguard Worker`;
25*6dbdd20aSAndroid Build Coastguard Worker
26*6dbdd20aSAndroid Build Coastguard Workerfunction getMetricsFromHash(): string[] {
27*6dbdd20aSAndroid Build Coastguard Worker  const metricVal = location.hash;
28*6dbdd20aSAndroid Build Coastguard Worker  const regex = new RegExp(`${PLUGIN_ID}:metrics=(.*)`);
29*6dbdd20aSAndroid Build Coastguard Worker  const match = metricVal.match(regex);
30*6dbdd20aSAndroid Build Coastguard Worker  if (match === null) {
31*6dbdd20aSAndroid Build Coastguard Worker    return [];
32*6dbdd20aSAndroid Build Coastguard Worker  }
33*6dbdd20aSAndroid Build Coastguard Worker  const capturedString = match[1];
34*6dbdd20aSAndroid Build Coastguard Worker  let metricList: string[] = [];
35*6dbdd20aSAndroid Build Coastguard Worker  if (capturedString.includes('--')) {
36*6dbdd20aSAndroid Build Coastguard Worker    metricList = capturedString.split('--');
37*6dbdd20aSAndroid Build Coastguard Worker  } else {
38*6dbdd20aSAndroid Build Coastguard Worker    metricList = [capturedString];
39*6dbdd20aSAndroid Build Coastguard Worker  }
40*6dbdd20aSAndroid Build Coastguard Worker  return metricList.map((metric) => decodeURIComponent(metric));
41*6dbdd20aSAndroid Build Coastguard Worker}
42*6dbdd20aSAndroid Build Coastguard Worker
43*6dbdd20aSAndroid Build Coastguard Workerlet metrics: string[];
44*6dbdd20aSAndroid Build Coastguard Worker
45*6dbdd20aSAndroid Build Coastguard Worker/**
46*6dbdd20aSAndroid Build Coastguard Worker * Plugin that adds and pins the debug track for the metric passed
47*6dbdd20aSAndroid Build Coastguard Worker * For more context -
48*6dbdd20aSAndroid Build Coastguard Worker * This plugin reads the names of regressed metrics from the url upon loading
49*6dbdd20aSAndroid Build Coastguard Worker * It then checks the metric names against some handlers and if they
50*6dbdd20aSAndroid Build Coastguard Worker * match it accordingly adds the debug tracks for them
51*6dbdd20aSAndroid Build Coastguard Worker * This way when comparing two different perfetto traces before and after
52*6dbdd20aSAndroid Build Coastguard Worker * the regression, the user will not have to manually search for the
53*6dbdd20aSAndroid Build Coastguard Worker * slices related to the regressed metric
54*6dbdd20aSAndroid Build Coastguard Worker */
55*6dbdd20aSAndroid Build Coastguard Workerexport default class implements PerfettoPlugin {
56*6dbdd20aSAndroid Build Coastguard Worker  static readonly id = PLUGIN_ID;
57*6dbdd20aSAndroid Build Coastguard Worker  static readonly dependencies = [AndroidCujsPlugin];
58*6dbdd20aSAndroid Build Coastguard Worker
59*6dbdd20aSAndroid Build Coastguard Worker  static onActivate(): void {
60*6dbdd20aSAndroid Build Coastguard Worker    metrics = getMetricsFromHash();
61*6dbdd20aSAndroid Build Coastguard Worker  }
62*6dbdd20aSAndroid Build Coastguard Worker
63*6dbdd20aSAndroid Build Coastguard Worker  async onTraceLoad(ctx: Trace) {
64*6dbdd20aSAndroid Build Coastguard Worker    ctx.commands.registerCommand({
65*6dbdd20aSAndroid Build Coastguard Worker      id: 'dev.perfetto.PinAndroidPerfMetrics#PinAndroidPerfMetrics',
66*6dbdd20aSAndroid Build Coastguard Worker      name: 'Add and Pin: Jank Metric Slice',
67*6dbdd20aSAndroid Build Coastguard Worker      callback: async (metric) => {
68*6dbdd20aSAndroid Build Coastguard Worker        metric = prompt('Metrics names (separated by comma)', '');
69*6dbdd20aSAndroid Build Coastguard Worker        if (metric === null) return;
70*6dbdd20aSAndroid Build Coastguard Worker        const metricList = metric.split(',');
71*6dbdd20aSAndroid Build Coastguard Worker        this.callHandlers(metricList, ctx);
72*6dbdd20aSAndroid Build Coastguard Worker      },
73*6dbdd20aSAndroid Build Coastguard Worker    });
74*6dbdd20aSAndroid Build Coastguard Worker    if (metrics.length !== 0) {
75*6dbdd20aSAndroid Build Coastguard Worker      this.callHandlers(metrics, ctx);
76*6dbdd20aSAndroid Build Coastguard Worker    }
77*6dbdd20aSAndroid Build Coastguard Worker  }
78*6dbdd20aSAndroid Build Coastguard Worker
79*6dbdd20aSAndroid Build Coastguard Worker  private async callHandlers(metricsList: string[], ctx: Trace) {
80*6dbdd20aSAndroid Build Coastguard Worker    // List of metrics that actually match some handler
81*6dbdd20aSAndroid Build Coastguard Worker    const metricsToShow: MetricHandlerMatch[] =
82*6dbdd20aSAndroid Build Coastguard Worker      this.getMetricsToShow(metricsList);
83*6dbdd20aSAndroid Build Coastguard Worker
84*6dbdd20aSAndroid Build Coastguard Worker    if (metricsToShow.length === 0) {
85*6dbdd20aSAndroid Build Coastguard Worker      return;
86*6dbdd20aSAndroid Build Coastguard Worker    }
87*6dbdd20aSAndroid Build Coastguard Worker
88*6dbdd20aSAndroid Build Coastguard Worker    await ctx.engine.query(JANK_CUJ_QUERY_PRECONDITIONS);
89*6dbdd20aSAndroid Build Coastguard Worker    for (const {metricData, metricHandler} of metricsToShow) {
90*6dbdd20aSAndroid Build Coastguard Worker      metricHandler.addMetricTrack(metricData, ctx);
91*6dbdd20aSAndroid Build Coastguard Worker    }
92*6dbdd20aSAndroid Build Coastguard Worker  }
93*6dbdd20aSAndroid Build Coastguard Worker
94*6dbdd20aSAndroid Build Coastguard Worker  private getMetricsToShow(metricList: string[]): MetricHandlerMatch[] {
95*6dbdd20aSAndroid Build Coastguard Worker    const sortedMetricList = [...metricList].sort();
96*6dbdd20aSAndroid Build Coastguard Worker    const validMetrics: MetricHandlerMatch[] = [];
97*6dbdd20aSAndroid Build Coastguard Worker    const alreadyMatchedMetricData: Set<string> = new Set();
98*6dbdd20aSAndroid Build Coastguard Worker    for (const metric of sortedMetricList) {
99*6dbdd20aSAndroid Build Coastguard Worker      for (const metricHandler of METRIC_HANDLERS) {
100*6dbdd20aSAndroid Build Coastguard Worker        const metricData = metricHandler.match(metric);
101*6dbdd20aSAndroid Build Coastguard Worker        if (!metricData) continue;
102*6dbdd20aSAndroid Build Coastguard Worker        const jsonMetricData = this.metricDataToJson(metricData);
103*6dbdd20aSAndroid Build Coastguard Worker        if (!alreadyMatchedMetricData.has(jsonMetricData)) {
104*6dbdd20aSAndroid Build Coastguard Worker          alreadyMatchedMetricData.add(jsonMetricData);
105*6dbdd20aSAndroid Build Coastguard Worker          validMetrics.push({
106*6dbdd20aSAndroid Build Coastguard Worker            metricData: metricData,
107*6dbdd20aSAndroid Build Coastguard Worker            metricHandler: metricHandler,
108*6dbdd20aSAndroid Build Coastguard Worker          });
109*6dbdd20aSAndroid Build Coastguard Worker        }
110*6dbdd20aSAndroid Build Coastguard Worker      }
111*6dbdd20aSAndroid Build Coastguard Worker    }
112*6dbdd20aSAndroid Build Coastguard Worker    return validMetrics;
113*6dbdd20aSAndroid Build Coastguard Worker  }
114*6dbdd20aSAndroid Build Coastguard Worker
115*6dbdd20aSAndroid Build Coastguard Worker  private metricDataToJson(metricData: MetricData): string {
116*6dbdd20aSAndroid Build Coastguard Worker    // Used to have a deterministic keys order.
117*6dbdd20aSAndroid Build Coastguard Worker    return JSON.stringify(metricData, Object.keys(metricData).sort());
118*6dbdd20aSAndroid Build Coastguard Worker  }
119*6dbdd20aSAndroid Build Coastguard Worker}
120