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