xref: /aosp_15_r20/external/perfetto/ui/src/plugins/dev.perfetto.DeeplinkQuerystring/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 Worker// When deep linking into Perfetto UI it is possible to pass arguments in the
16*6dbdd20aSAndroid Build Coastguard Worker// query string to automatically select a slice or run a query once the
17*6dbdd20aSAndroid Build Coastguard Worker// trace is loaded. This plugin deals with kicking off the relevant logic
18*6dbdd20aSAndroid Build Coastguard Worker// once the trace has loaded.
19*6dbdd20aSAndroid Build Coastguard Worker
20*6dbdd20aSAndroid Build Coastguard Workerimport {Trace} from '../../public/trace';
21*6dbdd20aSAndroid Build Coastguard Workerimport {PerfettoPlugin} from '../../public/plugin';
22*6dbdd20aSAndroid Build Coastguard Workerimport {addQueryResultsTab} from '../../components/query_table/query_result_tab';
23*6dbdd20aSAndroid Build Coastguard Workerimport {Time} from '../../base/time';
24*6dbdd20aSAndroid Build Coastguard Workerimport {RouteArgs} from '../../public/route_schema';
25*6dbdd20aSAndroid Build Coastguard Workerimport {App} from '../../public/app';
26*6dbdd20aSAndroid Build Coastguard Workerimport {exists} from '../../base/utils';
27*6dbdd20aSAndroid Build Coastguard Workerimport {NUM} from '../../trace_processor/query_result';
28*6dbdd20aSAndroid Build Coastguard Worker
29*6dbdd20aSAndroid Build Coastguard Workerlet routeArgsForFirstTrace: RouteArgs | undefined;
30*6dbdd20aSAndroid Build Coastguard Worker
31*6dbdd20aSAndroid Build Coastguard Worker/**
32*6dbdd20aSAndroid Build Coastguard Worker * Uses URL args (table, ts, dur) to select events on trace load.
33*6dbdd20aSAndroid Build Coastguard Worker *
34*6dbdd20aSAndroid Build Coastguard Worker * E.g. ?table=thread_state&ts=39978672284068&dur=18995809
35*6dbdd20aSAndroid Build Coastguard Worker *
36*6dbdd20aSAndroid Build Coastguard Worker * Note: `ts` and `dur` are used rather than id as id is not stable over TP
37*6dbdd20aSAndroid Build Coastguard Worker * versions.
38*6dbdd20aSAndroid Build Coastguard Worker *
39*6dbdd20aSAndroid Build Coastguard Worker * The table passed must have `ts`, `dur` (if a dur value is supplied) and `id`
40*6dbdd20aSAndroid Build Coastguard Worker * columns, and SQL resolvers must be available for those tables (usually from
41*6dbdd20aSAndroid Build Coastguard Worker * plugins).
42*6dbdd20aSAndroid Build Coastguard Worker */
43*6dbdd20aSAndroid Build Coastguard Workerexport default class implements PerfettoPlugin {
44*6dbdd20aSAndroid Build Coastguard Worker  static readonly id = 'dev.perfetto.DeeplinkQuerystring';
45*6dbdd20aSAndroid Build Coastguard Worker
46*6dbdd20aSAndroid Build Coastguard Worker  static onActivate(app: App): void {
47*6dbdd20aSAndroid Build Coastguard Worker    routeArgsForFirstTrace = app.initialRouteArgs;
48*6dbdd20aSAndroid Build Coastguard Worker  }
49*6dbdd20aSAndroid Build Coastguard Worker
50*6dbdd20aSAndroid Build Coastguard Worker  async onTraceLoad(trace: Trace) {
51*6dbdd20aSAndroid Build Coastguard Worker    trace.onTraceReady.addListener(async () => {
52*6dbdd20aSAndroid Build Coastguard Worker      const initialRouteArgs = routeArgsForFirstTrace;
53*6dbdd20aSAndroid Build Coastguard Worker      routeArgsForFirstTrace = undefined;
54*6dbdd20aSAndroid Build Coastguard Worker      if (initialRouteArgs === undefined) return;
55*6dbdd20aSAndroid Build Coastguard Worker
56*6dbdd20aSAndroid Build Coastguard Worker      await selectInitialRouteArgs(trace, initialRouteArgs);
57*6dbdd20aSAndroid Build Coastguard Worker      if (
58*6dbdd20aSAndroid Build Coastguard Worker        initialRouteArgs.visStart !== undefined &&
59*6dbdd20aSAndroid Build Coastguard Worker        initialRouteArgs.visEnd !== undefined
60*6dbdd20aSAndroid Build Coastguard Worker      ) {
61*6dbdd20aSAndroid Build Coastguard Worker        zoomPendingDeeplink(
62*6dbdd20aSAndroid Build Coastguard Worker          trace,
63*6dbdd20aSAndroid Build Coastguard Worker          initialRouteArgs.visStart,
64*6dbdd20aSAndroid Build Coastguard Worker          initialRouteArgs.visEnd,
65*6dbdd20aSAndroid Build Coastguard Worker        );
66*6dbdd20aSAndroid Build Coastguard Worker      }
67*6dbdd20aSAndroid Build Coastguard Worker      if (initialRouteArgs.query !== undefined) {
68*6dbdd20aSAndroid Build Coastguard Worker        addQueryResultsTab(trace, {
69*6dbdd20aSAndroid Build Coastguard Worker          query: initialRouteArgs.query,
70*6dbdd20aSAndroid Build Coastguard Worker          title: 'Deeplink Query',
71*6dbdd20aSAndroid Build Coastguard Worker        });
72*6dbdd20aSAndroid Build Coastguard Worker      }
73*6dbdd20aSAndroid Build Coastguard Worker    });
74*6dbdd20aSAndroid Build Coastguard Worker  }
75*6dbdd20aSAndroid Build Coastguard Worker}
76*6dbdd20aSAndroid Build Coastguard Worker
77*6dbdd20aSAndroid Build Coastguard Workerfunction zoomPendingDeeplink(trace: Trace, visStart: string, visEnd: string) {
78*6dbdd20aSAndroid Build Coastguard Worker  const visualStart = Time.fromRaw(BigInt(visStart));
79*6dbdd20aSAndroid Build Coastguard Worker  const visualEnd = Time.fromRaw(BigInt(visEnd));
80*6dbdd20aSAndroid Build Coastguard Worker  if (
81*6dbdd20aSAndroid Build Coastguard Worker    !(
82*6dbdd20aSAndroid Build Coastguard Worker      visualStart < visualEnd &&
83*6dbdd20aSAndroid Build Coastguard Worker      trace.traceInfo.start <= visualStart &&
84*6dbdd20aSAndroid Build Coastguard Worker      visualEnd <= trace.traceInfo.end
85*6dbdd20aSAndroid Build Coastguard Worker    )
86*6dbdd20aSAndroid Build Coastguard Worker  ) {
87*6dbdd20aSAndroid Build Coastguard Worker    return;
88*6dbdd20aSAndroid Build Coastguard Worker  }
89*6dbdd20aSAndroid Build Coastguard Worker  trace.timeline.setViewportTime(visualStart, visualEnd);
90*6dbdd20aSAndroid Build Coastguard Worker}
91*6dbdd20aSAndroid Build Coastguard Worker
92*6dbdd20aSAndroid Build Coastguard Workerasync function selectInitialRouteArgs(trace: Trace, args: RouteArgs) {
93*6dbdd20aSAndroid Build Coastguard Worker  const {table = 'slice', ts, dur} = args;
94*6dbdd20aSAndroid Build Coastguard Worker
95*6dbdd20aSAndroid Build Coastguard Worker  // We need at least a ts
96*6dbdd20aSAndroid Build Coastguard Worker  if (!exists(ts)) {
97*6dbdd20aSAndroid Build Coastguard Worker    return;
98*6dbdd20aSAndroid Build Coastguard Worker  }
99*6dbdd20aSAndroid Build Coastguard Worker
100*6dbdd20aSAndroid Build Coastguard Worker  const conditions = [];
101*6dbdd20aSAndroid Build Coastguard Worker  conditions.push(`ts = ${ts}`);
102*6dbdd20aSAndroid Build Coastguard Worker  exists(dur) && conditions.push(`dur = ${dur}`);
103*6dbdd20aSAndroid Build Coastguard Worker
104*6dbdd20aSAndroid Build Coastguard Worker  // Find the id of the slice with this ts & dur in the given table
105*6dbdd20aSAndroid Build Coastguard Worker  const result = await trace.engine.query(`
106*6dbdd20aSAndroid Build Coastguard Worker    select
107*6dbdd20aSAndroid Build Coastguard Worker      id
108*6dbdd20aSAndroid Build Coastguard Worker    from
109*6dbdd20aSAndroid Build Coastguard Worker      ${table}
110*6dbdd20aSAndroid Build Coastguard Worker    where ${conditions.join(' AND ')}
111*6dbdd20aSAndroid Build Coastguard Worker  `);
112*6dbdd20aSAndroid Build Coastguard Worker
113*6dbdd20aSAndroid Build Coastguard Worker  if (result.numRows() === 0) {
114*6dbdd20aSAndroid Build Coastguard Worker    return;
115*6dbdd20aSAndroid Build Coastguard Worker  }
116*6dbdd20aSAndroid Build Coastguard Worker
117*6dbdd20aSAndroid Build Coastguard Worker  const {id} = result.firstRow({
118*6dbdd20aSAndroid Build Coastguard Worker    id: NUM,
119*6dbdd20aSAndroid Build Coastguard Worker  });
120*6dbdd20aSAndroid Build Coastguard Worker
121*6dbdd20aSAndroid Build Coastguard Worker  trace.selection.selectSqlEvent(table, id, {
122*6dbdd20aSAndroid Build Coastguard Worker    scrollToSelection: true,
123*6dbdd20aSAndroid Build Coastguard Worker    switchToCurrentSelectionTab: false,
124*6dbdd20aSAndroid Build Coastguard Worker  });
125*6dbdd20aSAndroid Build Coastguard Worker}
126