1// Copyright (C) 2023 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 m from 'mithril'; 16 17import {BigintMath} from '../../base/bigint_math'; 18import {Icons} from '../../base/semantic_icons'; 19import {duration, Time, time} from '../../base/time'; 20import {exists} from '../../base/utils'; 21import {Engine} from '../../trace_processor/engine'; 22import { 23 LONG, 24 LONG_NULL, 25 NUM, 26 NUM_NULL, 27 STR, 28 STR_NULL, 29} from '../../trace_processor/query_result'; 30import { 31 constraintsToQuerySuffix, 32 SQLConstraints, 33} from '../../trace_processor/sql_utils'; 34import {Anchor} from '../../widgets/anchor'; 35import {globals} from '../globals'; 36import {focusHorizontalRange, verticalScrollToTrack} from '../scroll_helper'; 37import { 38 asArgSetId, 39 asSliceSqlId, 40 asUpid, 41 asUtid, 42 SliceSqlId, 43 Upid, 44 Utid, 45} from '../sql_types'; 46import { 47 getProcessInfo, 48 getThreadInfo, 49 ProcessInfo, 50 ThreadInfo, 51} from '../thread_and_process_info'; 52 53import {Arg, getArgs} from './args'; 54 55// Basic information about a slice. 56export interface SliceDetails { 57 id: SliceSqlId; 58 name: string; 59 ts: time; 60 absTime?: string; 61 dur: duration; 62 parentId?: SliceSqlId; 63 trackId: number; 64 depth: number; 65 thread?: ThreadInfo; 66 process?: ProcessInfo; 67 threadTs?: time; 68 threadDur?: duration; 69 category?: string; 70 args?: Arg[]; 71} 72 73async function getUtidAndUpid( 74 engine: Engine, 75 sqlTrackId: number, 76): Promise<{utid?: Utid; upid?: Upid}> { 77 const columnInfo = ( 78 await engine.query(` 79 WITH 80 leafTrackTable AS (SELECT type FROM track WHERE id = ${sqlTrackId}), 81 cols AS ( 82 SELECT name 83 FROM pragma_table_info((SELECT type FROM leafTrackTable)) 84 ) 85 SELECT 86 type as leafTrackTable, 87 'upid' in cols AS hasUpid, 88 'utid' in cols AS hasUtid 89 FROM leafTrackTable 90 `) 91 ).firstRow({hasUpid: NUM, hasUtid: NUM, leafTrackTable: STR}); 92 const hasUpid = columnInfo.hasUpid !== 0; 93 const hasUtid = columnInfo.hasUtid !== 0; 94 95 const result: {utid?: Utid; upid?: Upid} = {}; 96 97 if (hasUtid) { 98 const utid = ( 99 await engine.query(` 100 SELECT utid 101 FROM ${columnInfo.leafTrackTable} 102 WHERE id = ${sqlTrackId}; 103 `) 104 ).firstRow({ 105 utid: NUM, 106 }).utid; 107 result.utid = asUtid(utid); 108 } else if (hasUpid) { 109 const upid = ( 110 await engine.query(` 111 SELECT upid 112 FROM ${columnInfo.leafTrackTable} 113 WHERE id = ${sqlTrackId}; 114 `) 115 ).firstRow({ 116 upid: NUM, 117 }).upid; 118 result.upid = asUpid(upid); 119 } 120 return result; 121} 122 123export async function getSliceFromConstraints( 124 engine: Engine, 125 constraints: SQLConstraints, 126): Promise<SliceDetails[]> { 127 const query = await engine.query(` 128 SELECT 129 id, 130 name, 131 ts, 132 dur, 133 track_id as trackId, 134 depth, 135 parent_id as parentId, 136 thread_dur as threadDur, 137 thread_ts as threadTs, 138 category, 139 arg_set_id as argSetId, 140 ABS_TIME_STR(ts) as absTime 141 FROM slice 142 ${constraintsToQuerySuffix(constraints)}`); 143 const it = query.iter({ 144 id: NUM, 145 name: STR, 146 ts: LONG, 147 dur: LONG, 148 trackId: NUM, 149 depth: NUM, 150 parentId: NUM_NULL, 151 threadDur: LONG_NULL, 152 threadTs: LONG_NULL, 153 category: STR_NULL, 154 argSetId: NUM, 155 absTime: STR_NULL, 156 }); 157 158 const result: SliceDetails[] = []; 159 for (; it.valid(); it.next()) { 160 const {utid, upid} = await getUtidAndUpid(engine, it.trackId); 161 162 const thread: ThreadInfo | undefined = 163 utid === undefined ? undefined : await getThreadInfo(engine, utid); 164 const process: ProcessInfo | undefined = 165 thread !== undefined 166 ? thread.process 167 : upid === undefined 168 ? undefined 169 : await getProcessInfo(engine, upid); 170 171 result.push({ 172 id: asSliceSqlId(it.id), 173 name: it.name, 174 ts: Time.fromRaw(it.ts), 175 dur: it.dur, 176 trackId: it.trackId, 177 depth: it.depth, 178 parentId: asSliceSqlId(it.parentId ?? undefined), 179 thread, 180 process, 181 threadDur: it.threadDur ?? undefined, 182 threadTs: exists(it.threadTs) ? Time.fromRaw(it.threadTs) : undefined, 183 category: it.category ?? undefined, 184 args: await getArgs(engine, asArgSetId(it.argSetId)), 185 absTime: it.absTime ?? undefined, 186 }); 187 } 188 return result; 189} 190 191export async function getSlice( 192 engine: Engine, 193 id: SliceSqlId, 194): Promise<SliceDetails | undefined> { 195 const result = await getSliceFromConstraints(engine, { 196 filters: [`id=${id}`], 197 }); 198 if (result.length > 1) { 199 throw new Error(`slice table has more than one row with id ${id}`); 200 } 201 if (result.length === 0) { 202 return undefined; 203 } 204 return result[0]; 205} 206 207interface SliceRefAttrs { 208 readonly id: SliceSqlId; 209 readonly name: string; 210 readonly ts: time; 211 readonly dur: duration; 212 readonly sqlTrackId: number; 213 214 // Whether clicking on the reference should change the current tab 215 // to "current selection" tab in addition to updating the selection 216 // and changing the viewport. True by default. 217 readonly switchToCurrentSelectionTab?: boolean; 218} 219 220export class SliceRef implements m.ClassComponent<SliceRefAttrs> { 221 view(vnode: m.Vnode<SliceRefAttrs>) { 222 const switchTab = vnode.attrs.switchToCurrentSelectionTab ?? true; 223 return m( 224 Anchor, 225 { 226 icon: Icons.UpdateSelection, 227 onclick: () => { 228 const trackKeyByTrackId = globals.trackManager.trackKeyByTrackId; 229 const trackKey = trackKeyByTrackId.get(vnode.attrs.sqlTrackId); 230 if (trackKey === undefined) return; 231 verticalScrollToTrack(trackKey, true); 232 // Clamp duration to 1 - i.e. for instant events 233 const dur = BigintMath.max(1n, vnode.attrs.dur); 234 focusHorizontalRange( 235 vnode.attrs.ts, 236 Time.fromRaw(vnode.attrs.ts + dur), 237 ); 238 239 globals.setLegacySelection( 240 { 241 kind: 'SLICE', 242 id: vnode.attrs.id, 243 trackKey, 244 table: 'slice', 245 }, 246 { 247 clearSearch: true, 248 pendingScrollId: undefined, 249 switchToCurrentSelectionTab: switchTab, 250 }, 251 ); 252 }, 253 }, 254 vnode.attrs.name, 255 ); 256 } 257} 258 259export function sliceRef(slice: SliceDetails, name?: string): m.Child { 260 return m(SliceRef, { 261 id: slice.id, 262 name: name ?? slice.name, 263 ts: slice.ts, 264 dur: slice.dur, 265 sqlTrackId: slice.trackId, 266 }); 267} 268 269// A slice tree node, combining the information about the given slice with 270// information about its descendants. 271export interface SliceTreeNode extends SliceDetails { 272 children: SliceTreeNode[]; 273 parent?: SliceTreeNode; 274} 275 276// Get all descendants for a given slice in a tree form. 277export async function getDescendantSliceTree( 278 engine: Engine, 279 id: SliceSqlId, 280): Promise<SliceTreeNode | undefined> { 281 const slice = await getSlice(engine, id); 282 if (slice === undefined) { 283 return undefined; 284 } 285 const descendants = await getSliceFromConstraints(engine, { 286 filters: [ 287 `track_id=${slice.trackId}`, 288 `depth >= ${slice.depth}`, 289 `ts >= ${slice.ts}`, 290 // TODO(altimin): consider making `dur` undefined here instead of -1. 291 slice.dur >= 0 ? `ts <= (${slice.ts} + ${slice.dur})` : undefined, 292 ], 293 orderBy: ['ts', 'depth'], 294 }); 295 const slices: {[key: SliceSqlId]: SliceTreeNode} = Object.fromEntries( 296 descendants.map((slice) => [ 297 slice.id, 298 { 299 children: [], 300 ...slice, 301 }, 302 ]), 303 ); 304 for (const [_, slice] of Object.entries(slices)) { 305 if (slice.parentId !== undefined) { 306 const parent = slices[slice.parentId]; 307 slice.parent = parent; 308 parent.children.push(slice); 309 } 310 } 311 return slices[id]; 312} 313