// Copyright (C) 2023 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import m from 'mithril'; import {copyToClipboard} from '../base/clipboard'; import {Icons} from '../base/semantic_icons'; import {exists} from '../base/utils'; import {Engine} from '../trace_processor/engine'; import {NUM, NUM_NULL, STR, STR_NULL} from '../trace_processor/query_result'; import {fromNumNull} from '../trace_processor/sql_utils'; import {Anchor} from '../widgets/anchor'; import {MenuItem, PopupMenu2} from '../widgets/menu'; import {Upid, Utid} from './sql_types'; // Interface definitions for process and thread-related information // and functions to extract them from SQL. // TODO(altimin): Current implementation ends up querying process and thread // information separately for each thread. Given that there is a limited // numer of threads and processes, it might be easier to fetch this information // once when loading the trace and then just look it up synchronously. export interface ProcessInfo { upid: Upid; pid?: number; name?: string; uid?: number; packageName?: string; versionCode?: number; } export async function getProcessInfo( engine: Engine, upid: Upid, ): Promise { const it = ( await engine.query(` SELECT pid, name, uid FROM process WHERE upid = ${upid}; `) ).iter({pid: NUM, name: STR_NULL, uid: NUM_NULL}); if (!it.valid()) { return {upid}; } const result: ProcessInfo = { upid, pid: it.pid, name: it.name || undefined, }; if (it.pid === null) { return result; } result.pid = it.pid || undefined; if (it.uid === undefined) { return result; } const packageResult = await engine.query(` SELECT package_name as packageName, version_code as versionCode FROM package_list WHERE uid = ${it.uid}; `); // The package_list table is not populated in some traces so we need to // check if the result has returned any rows. if (packageResult.numRows() > 0) { const packageDetails = packageResult.firstRow({ packageName: STR, versionCode: NUM, }); result.packageName = packageDetails.packageName; result.versionCode = packageDetails.versionCode || undefined; } return result; } function getDisplayName( name: string | undefined, id: number | undefined, ): string | undefined { if (name === undefined) { return id === undefined ? undefined : `${id}`; } return id === undefined ? name : `${name} [${id}]`; } export function renderProcessRef(info: ProcessInfo): m.Children { const name = info.name; return m( PopupMenu2, { trigger: m(Anchor, getProcessName(info)), }, exists(name) && m(MenuItem, { icon: Icons.Copy, label: 'Copy process name', onclick: () => copyToClipboard(name), }), exists(info.pid) && m(MenuItem, { icon: Icons.Copy, label: 'Copy pid', onclick: () => copyToClipboard(`${info.pid}`), }), m(MenuItem, { icon: Icons.Copy, label: 'Copy upid', onclick: () => copyToClipboard(`${info.upid}`), }), ); } export function getProcessName(info?: ProcessInfo): string | undefined { return getDisplayName(info?.name, info?.pid); } export interface ThreadInfo { utid: Utid; tid?: number; name?: string; process?: ProcessInfo; } export async function getThreadInfo( engine: Engine, utid: Utid, ): Promise { const it = ( await engine.query(` SELECT tid, name, upid FROM thread WHERE utid = ${utid}; `) ).iter({tid: NUM, name: STR_NULL, upid: NUM_NULL}); if (!it.valid()) { return { utid, }; } const upid = fromNumNull(it.upid) as Upid | undefined; return { utid, tid: it.tid, name: it.name || undefined, process: upid ? await getProcessInfo(engine, upid) : undefined, }; } export function renderThreadRef(info: ThreadInfo): m.Children { const name = info.name; return m( PopupMenu2, { trigger: m(Anchor, getThreadName(info)), }, exists(name) && m(MenuItem, { icon: Icons.Copy, label: 'Copy thread name', onclick: () => copyToClipboard(name), }), exists(info.tid) && m(MenuItem, { icon: Icons.Copy, label: 'Copy tid', onclick: () => copyToClipboard(`${info.tid}`), }), m(MenuItem, { icon: Icons.Copy, label: 'Copy utid', onclick: () => copyToClipboard(`${info.utid}`), }), ); } export function getThreadName(info?: ThreadInfo): string | undefined { return getDisplayName(info?.name, info?.tid); } // Return the full thread name, including the process name. export function getFullThreadName(info?: ThreadInfo): string | undefined { if (info?.process === undefined) { return getThreadName(info); } return `${getThreadName(info)} ${getProcessName(info.process)}`; }