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