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