• 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 {copyToClipboard} from '../base/clipboard';
18import {Icons} from '../base/semantic_icons';
19import {exists} from '../base/utils';
20import {Engine} from '../trace_processor/engine';
21import {NUM, NUM_NULL, STR, STR_NULL} from '../trace_processor/query_result';
22import {fromNumNull} from '../trace_processor/sql_utils';
23import {Anchor} from '../widgets/anchor';
24import {MenuItem, PopupMenu2} from '../widgets/menu';
25
26import {Upid, Utid} from './sql_types';
27
28// Interface definitions for process and thread-related information
29// and functions to extract them from SQL.
30
31// TODO(altimin): Current implementation ends up querying process and thread
32// information separately for each thread. Given that there is a limited
33// numer of threads and processes, it might be easier to fetch this information
34// once when loading the trace and then just look it up synchronously.
35
36export interface ProcessInfo {
37  upid: Upid;
38  pid?: number;
39  name?: string;
40  uid?: number;
41  packageName?: string;
42  versionCode?: number;
43}
44
45export async function getProcessInfo(
46  engine: Engine,
47  upid: Upid,
48): Promise<ProcessInfo> {
49  const it = (
50    await engine.query(`
51              SELECT pid, name, uid FROM process WHERE upid = ${upid};
52            `)
53  ).iter({pid: NUM, name: STR_NULL, uid: NUM_NULL});
54  if (!it.valid()) {
55    return {upid};
56  }
57  const result: ProcessInfo = {
58    upid,
59    pid: it.pid,
60    name: it.name || undefined,
61  };
62
63  if (it.pid === null) {
64    return result;
65  }
66  result.pid = it.pid || undefined;
67
68  if (it.uid === undefined) {
69    return result;
70  }
71
72  const packageResult = await engine.query(`
73                SELECT
74                  package_name as packageName,
75                  version_code as versionCode
76                FROM package_list WHERE uid = ${it.uid};
77              `);
78  // The package_list table is not populated in some traces so we need to
79  // check if the result has returned any rows.
80  if (packageResult.numRows() > 0) {
81    const packageDetails = packageResult.firstRow({
82      packageName: STR,
83      versionCode: NUM,
84    });
85    result.packageName = packageDetails.packageName;
86    result.versionCode = packageDetails.versionCode || undefined;
87  }
88  return result;
89}
90
91function getDisplayName(
92  name: string | undefined,
93  id: number | undefined,
94): string | undefined {
95  if (name === undefined) {
96    return id === undefined ? undefined : `${id}`;
97  }
98  return id === undefined ? name : `${name} [${id}]`;
99}
100
101export function renderProcessRef(info: ProcessInfo): m.Children {
102  const name = info.name;
103  return m(
104    PopupMenu2,
105    {
106      trigger: m(Anchor, getProcessName(info)),
107    },
108    exists(name) &&
109      m(MenuItem, {
110        icon: Icons.Copy,
111        label: 'Copy process name',
112        onclick: () => copyToClipboard(name),
113      }),
114    exists(info.pid) &&
115      m(MenuItem, {
116        icon: Icons.Copy,
117        label: 'Copy pid',
118        onclick: () => copyToClipboard(`${info.pid}`),
119      }),
120    m(MenuItem, {
121      icon: Icons.Copy,
122      label: 'Copy upid',
123      onclick: () => copyToClipboard(`${info.upid}`),
124    }),
125  );
126}
127
128export function getProcessName(info?: ProcessInfo): string | undefined {
129  return getDisplayName(info?.name, info?.pid);
130}
131
132export interface ThreadInfo {
133  utid: Utid;
134  tid?: number;
135  name?: string;
136  process?: ProcessInfo;
137}
138
139export async function getThreadInfo(
140  engine: Engine,
141  utid: Utid,
142): Promise<ThreadInfo> {
143  const it = (
144    await engine.query(`
145        SELECT tid, name, upid
146        FROM thread
147        WHERE utid = ${utid};
148    `)
149  ).iter({tid: NUM, name: STR_NULL, upid: NUM_NULL});
150  if (!it.valid()) {
151    return {
152      utid,
153    };
154  }
155  const upid = fromNumNull(it.upid) as Upid | undefined;
156  return {
157    utid,
158    tid: it.tid,
159    name: it.name || undefined,
160    process: upid ? await getProcessInfo(engine, upid) : undefined,
161  };
162}
163
164export function renderThreadRef(info: ThreadInfo): m.Children {
165  const name = info.name;
166  return m(
167    PopupMenu2,
168    {
169      trigger: m(Anchor, getThreadName(info)),
170    },
171    exists(name) &&
172      m(MenuItem, {
173        icon: Icons.Copy,
174        label: 'Copy thread name',
175        onclick: () => copyToClipboard(name),
176      }),
177    exists(info.tid) &&
178      m(MenuItem, {
179        icon: Icons.Copy,
180        label: 'Copy tid',
181        onclick: () => copyToClipboard(`${info.tid}`),
182      }),
183    m(MenuItem, {
184      icon: Icons.Copy,
185      label: 'Copy utid',
186      onclick: () => copyToClipboard(`${info.utid}`),
187    }),
188  );
189}
190
191export function getThreadName(info?: ThreadInfo): string | undefined {
192  return getDisplayName(info?.name, info?.tid);
193}
194
195// Return the full thread name, including the process name.
196export function getFullThreadName(info?: ThreadInfo): string | undefined {
197  if (info?.process === undefined) {
198    return getThreadName(info);
199  }
200  return `${getThreadName(info)} ${getProcessName(info.process)}`;
201}
202