xref: /aosp_15_r20/external/perfetto/ui/src/components/sql_utils/slice.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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 columnInfo = (
66    await engine.query(`
67    WITH
68       leafTrackTable AS (SELECT type FROM track WHERE id = ${sqlTrackId}),
69       cols AS (
70            SELECT name
71            FROM pragma_table_info((SELECT type FROM leafTrackTable))
72        )
73    SELECT
74       type as leafTrackTable,
75      'upid' in cols AS hasUpid,
76      'utid' in cols AS hasUtid
77    FROM leafTrackTable
78  `)
79  ).firstRow({hasUpid: NUM, hasUtid: NUM, leafTrackTable: STR});
80  const hasUpid = columnInfo.hasUpid !== 0;
81  const hasUtid = columnInfo.hasUtid !== 0;
82
83  const result: {utid?: Utid; upid?: Upid} = {};
84
85  if (hasUtid) {
86    const utid = (
87      await engine.query(`
88        SELECT utid
89        FROM ${columnInfo.leafTrackTable}
90        WHERE id = ${sqlTrackId};
91    `)
92    ).firstRow({
93      utid: NUM,
94    }).utid;
95    result.utid = asUtid(utid);
96  } else if (hasUpid) {
97    const upid = (
98      await engine.query(`
99        SELECT upid
100        FROM ${columnInfo.leafTrackTable}
101        WHERE id = ${sqlTrackId};
102    `)
103    ).firstRow({
104      upid: NUM,
105    }).upid;
106    result.upid = asUpid(upid);
107  }
108  return result;
109}
110
111export async function getSliceFromConstraints(
112  engine: Engine,
113  constraints: SQLConstraints,
114): Promise<SliceDetails[]> {
115  const query = await engine.query(`
116    SELECT
117      id,
118      name,
119      ts,
120      dur,
121      track_id as trackId,
122      depth,
123      parent_id as parentId,
124      thread_dur as threadDur,
125      thread_ts as threadTs,
126      category,
127      arg_set_id as argSetId,
128      ABS_TIME_STR(ts) as absTime
129    FROM slice
130    ${constraintsToQuerySuffix(constraints)}`);
131  const it = query.iter({
132    id: NUM,
133    name: STR,
134    ts: LONG,
135    dur: LONG,
136    trackId: NUM,
137    depth: NUM,
138    parentId: NUM_NULL,
139    threadDur: LONG_NULL,
140    threadTs: LONG_NULL,
141    category: STR_NULL,
142    argSetId: NUM,
143    absTime: STR_NULL,
144  });
145
146  const result: SliceDetails[] = [];
147  for (; it.valid(); it.next()) {
148    const {utid, upid} = await getUtidAndUpid(engine, it.trackId);
149
150    const thread: ThreadInfo | undefined =
151      utid === undefined ? undefined : await getThreadInfo(engine, utid);
152    const process: ProcessInfo | undefined =
153      thread !== undefined
154        ? thread.process
155        : upid === undefined
156          ? undefined
157          : await getProcessInfo(engine, upid);
158
159    result.push({
160      id: asSliceSqlId(it.id),
161      name: it.name,
162      ts: Time.fromRaw(it.ts),
163      dur: it.dur,
164      trackId: it.trackId,
165      depth: it.depth,
166      parentId: asSliceSqlId(it.parentId ?? undefined),
167      thread,
168      process,
169      threadDur: it.threadDur ?? undefined,
170      threadTs: exists(it.threadTs) ? Time.fromRaw(it.threadTs) : undefined,
171      category: it.category ?? undefined,
172      args: await getArgs(engine, asArgSetId(it.argSetId)),
173      absTime: it.absTime ?? undefined,
174    });
175  }
176  return result;
177}
178
179export async function getSlice(
180  engine: Engine,
181  id: SliceSqlId,
182): Promise<SliceDetails | undefined> {
183  const result = await getSliceFromConstraints(engine, {
184    filters: [`id=${id}`],
185  });
186  if (result.length > 1) {
187    throw new Error(`slice table has more than one row with id ${id}`);
188  }
189  if (result.length === 0) {
190    return undefined;
191  }
192  return result[0];
193}
194
195// A slice tree node, combining the information about the given slice with
196// information about its descendants.
197export interface SliceTreeNode extends SliceDetails {
198  children: SliceTreeNode[];
199  parent?: SliceTreeNode;
200}
201
202// Get all descendants for a given slice in a tree form.
203export async function getDescendantSliceTree(
204  engine: Engine,
205  id: SliceSqlId,
206): Promise<SliceTreeNode | undefined> {
207  const slice = await getSlice(engine, id);
208  if (slice === undefined) {
209    return undefined;
210  }
211  const descendants = await getSliceFromConstraints(engine, {
212    filters: [
213      `track_id=${slice.trackId}`,
214      `depth >= ${slice.depth}`,
215      `ts >= ${slice.ts}`,
216      // TODO(altimin): consider making `dur` undefined here instead of -1.
217      slice.dur >= 0 ? `ts <= (${slice.ts} + ${slice.dur})` : undefined,
218    ],
219    orderBy: ['ts', 'depth'],
220  });
221  const slices: {[key: SliceSqlId]: SliceTreeNode} = Object.fromEntries(
222    descendants.map((slice) => [
223      slice.id,
224      {
225        children: [],
226        ...slice,
227      },
228    ]),
229  );
230  for (const [_, slice] of Object.entries(slices)) {
231    if (slice.parentId !== undefined) {
232      const parent = slices[slice.parentId];
233      slice.parent = parent;
234      parent.children.push(slice);
235    }
236  }
237  return slices[id];
238}
239