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 {duration, Time, time} from '../../base/time'; 16import {exists} from '../../base/utils'; 17import {Engine} from '../../trace_processor/engine'; 18import { 19 LONG, 20 LONG_NULL, 21 NUM, 22 NUM_NULL, 23 STR, 24 STR_NULL, 25} from '../../trace_processor/query_result'; 26import { 27 constraintsToQuerySuffix, 28 SQLConstraints, 29} from '../../trace_processor/sql_utils'; 30import { 31 asArgSetId, 32 asSliceSqlId, 33 asUpid, 34 asUtid, 35 SliceSqlId, 36 Upid, 37 Utid, 38} from './core_types'; 39import {Arg, getArgs} from './args'; 40import {getThreadInfo, ThreadInfo} from './thread'; 41import {getProcessInfo, ProcessInfo} from './process'; 42 43// Basic information about a slice. 44export interface SliceDetails { 45 id: SliceSqlId; 46 name: string; 47 ts: time; 48 absTime?: string; 49 dur: duration; 50 parentId?: SliceSqlId; 51 trackId: number; 52 depth: number; 53 thread?: ThreadInfo; 54 process?: ProcessInfo; 55 threadTs?: time; 56 threadDur?: duration; 57 category?: string; 58 args?: Arg[]; 59} 60 61async function getUtidAndUpid( 62 engine: Engine, 63 sqlTrackId: number, 64): Promise<{utid?: Utid; upid?: Upid}> { 65 const {upid, utid} = ( 66 await engine.query(` 67 SELECT 68 extract_arg(dimension_arg_set_id, 'upid') as upid, 69 extract_arg(dimension_arg_set_id, 'utid') as utid 70 FROM track 71 WHERE id = ${sqlTrackId} 72 `) 73 ).firstRow({upid: NUM_NULL, utid: NUM_NULL}); 74 return {upid: asUpid(upid ?? undefined), utid: asUtid(utid ?? undefined)}; 75} 76 77export async function getSliceFromConstraints( 78 engine: Engine, 79 constraints: SQLConstraints, 80): Promise<SliceDetails[]> { 81 const query = await engine.query(` 82 SELECT 83 id, 84 name, 85 ts, 86 dur, 87 track_id as trackId, 88 depth, 89 parent_id as parentId, 90 thread_dur as threadDur, 91 thread_ts as threadTs, 92 category, 93 arg_set_id as argSetId, 94 ABS_TIME_STR(ts) as absTime 95 FROM slice 96 ${constraintsToQuerySuffix(constraints)}`); 97 const it = query.iter({ 98 id: NUM, 99 name: STR, 100 ts: LONG, 101 dur: LONG, 102 trackId: NUM, 103 depth: NUM, 104 parentId: NUM_NULL, 105 threadDur: LONG_NULL, 106 threadTs: LONG_NULL, 107 category: STR_NULL, 108 argSetId: NUM_NULL, 109 absTime: STR_NULL, 110 }); 111 112 const result: SliceDetails[] = []; 113 for (; it.valid(); it.next()) { 114 const {utid, upid} = await getUtidAndUpid(engine, it.trackId); 115 116 const thread: ThreadInfo | undefined = 117 utid === undefined ? undefined : await getThreadInfo(engine, utid); 118 const process: ProcessInfo | undefined = 119 thread !== undefined 120 ? thread.process 121 : upid === undefined 122 ? undefined 123 : await getProcessInfo(engine, upid); 124 125 result.push({ 126 id: asSliceSqlId(it.id), 127 name: it.name, 128 ts: Time.fromRaw(it.ts), 129 dur: it.dur, 130 trackId: it.trackId, 131 depth: it.depth, 132 parentId: asSliceSqlId(it.parentId ?? undefined), 133 thread, 134 process, 135 threadDur: it.threadDur ?? undefined, 136 threadTs: exists(it.threadTs) ? Time.fromRaw(it.threadTs) : undefined, 137 category: it.category ?? undefined, 138 args: exists(it.argSetId) 139 ? await getArgs(engine, asArgSetId(it.argSetId)) 140 : undefined, 141 absTime: it.absTime ?? undefined, 142 }); 143 } 144 return result; 145} 146 147export async function getSlice( 148 engine: Engine, 149 id: SliceSqlId, 150): Promise<SliceDetails | undefined> { 151 const result = await getSliceFromConstraints(engine, { 152 filters: [`id=${id}`], 153 }); 154 if (result.length > 1) { 155 throw new Error(`slice table has more than one row with id ${id}`); 156 } 157 if (result.length === 0) { 158 return undefined; 159 } 160 return result[0]; 161} 162 163// A slice tree node, combining the information about the given slice with 164// information about its descendants. 165export interface SliceTreeNode extends SliceDetails { 166 children: SliceTreeNode[]; 167 parent?: SliceTreeNode; 168} 169 170// Get all descendants for a given slice in a tree form. 171export async function getDescendantSliceTree( 172 engine: Engine, 173 id: SliceSqlId, 174): Promise<SliceTreeNode | undefined> { 175 const slice = await getSlice(engine, id); 176 if (slice === undefined) { 177 return undefined; 178 } 179 const descendants = await getSliceFromConstraints(engine, { 180 filters: [ 181 `track_id=${slice.trackId}`, 182 `depth >= ${slice.depth}`, 183 `ts >= ${slice.ts}`, 184 // TODO(altimin): consider making `dur` undefined here instead of -1. 185 slice.dur >= 0 ? `ts <= (${slice.ts} + ${slice.dur})` : undefined, 186 ], 187 orderBy: ['ts', 'depth'], 188 }); 189 const slices: {[key: SliceSqlId]: SliceTreeNode} = Object.fromEntries( 190 descendants.map((slice) => [ 191 slice.id, 192 { 193 children: [], 194 ...slice, 195 }, 196 ]), 197 ); 198 for (const [_, slice] of Object.entries(slices)) { 199 if (slice.parentId !== undefined) { 200 const parent = slices[slice.parentId]; 201 slice.parent = parent; 202 parent.children.push(slice); 203 } 204 } 205 return slices[id]; 206} 207