• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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