1// Copyright (C) 2024 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 {Trace} from '../../public/trace'; 16import {PerfettoPlugin} from '../../public/plugin'; 17import { 18 ProcessDesc, 19 ThreadDesc, 20 ThreadMap, 21} from '../dev.perfetto.Thread/threads'; 22import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result'; 23import {assertExists} from '../../base/logging'; 24 25async function listThreads(trace: Trace) { 26 const query = ` 27 select 28 utid, 29 tid, 30 pid, 31 ifnull(thread.name, '') as threadName, 32 ifnull( 33 case when length(process.name) > 0 then process.name else null end, 34 thread.name) as procName, 35 process.cmdline as cmdline 36 from (select * from thread order by upid) as thread 37 left join (select * from process order by upid) as process using(upid) 38 `; 39 const result = await trace.engine.query(query); 40 const threads = new Map<number, ThreadDesc>(); 41 const it = result.iter({ 42 utid: NUM, 43 tid: NUM, 44 pid: NUM_NULL, 45 threadName: STR, 46 procName: STR_NULL, 47 cmdline: STR_NULL, 48 }); 49 for (; it.valid(); it.next()) { 50 const utid = it.utid; 51 const tid = it.tid; 52 const pid = it.pid === null ? undefined : it.pid; 53 const threadName = it.threadName; 54 const procName = it.procName === null ? undefined : it.procName; 55 const cmdline = it.cmdline === null ? undefined : it.cmdline; 56 threads.set(utid, {utid, tid, threadName, pid, procName, cmdline}); 57 } 58 return threads; 59} 60 61async function listProcesses(trace: Trace) { 62 const query = ` 63 select 64 upid, 65 pid, 66 case 67 when length(process.name) > 0 then process.name 68 else null 69 end as procName, 70 cmdline 71 from process 72 `; 73 const result = await trace.engine.query(query); 74 const processMap = new Map<number, ProcessDesc>(); 75 const it = result.iter({ 76 upid: NUM, 77 pid: NUM, 78 procName: STR_NULL, 79 cmdline: STR_NULL, 80 }); 81 for (; it.valid(); it.next()) { 82 processMap.set(it.upid, { 83 upid: it.upid, 84 pid: it.pid, 85 procName: it.procName ?? undefined, 86 cmdline: it.cmdline ?? undefined, 87 }); 88 } 89 return processMap; 90} 91 92export default class implements PerfettoPlugin { 93 static readonly id = 'dev.perfetto.Thread'; 94 private threads?: ThreadMap; 95 96 async onTraceLoad(trace: Trace) { 97 const threadMap = await listThreads(trace); 98 const processMap = await listProcesses(trace); 99 this.threads = threadMap; 100 101 // Add a track filter criteria so that tracks may be filtered by process. 102 trace.tracks.registerTrackFilterCriteria({ 103 name: 'Process', 104 options: Array.from(processMap.entries()).map(([upid, process]) => { 105 const procName = process.procName ?? '<no name>'; 106 return { 107 key: upid.toString(), 108 label: `[${upid}] ${procName}`, 109 }; 110 }), 111 predicate: (node, filterTerm) => { 112 if (node.uri === undefined) return false; 113 const track = trace.tracks.getTrack(node.uri); 114 if (!track) return false; 115 return track.tags?.upid === Number(filterTerm); 116 }, 117 }); 118 119 // Add a track filter criteria so that tracks may be filtered by thread. 120 trace.tracks.registerTrackFilterCriteria({ 121 name: 'Thread', 122 options: Array.from(threadMap.entries()).map(([utid, thread]) => { 123 const procName = thread.threadName ?? '<no name>'; 124 return { 125 key: utid.toString(), 126 label: `[${utid}] ${procName}`, 127 }; 128 }), 129 predicate: (node, filterTerm) => { 130 if (node.uri === undefined) return false; 131 const track = trace.tracks.getTrack(node.uri); 132 if (!track) return false; 133 return track.tags?.utid === Number(filterTerm); 134 }, 135 }); 136 } 137 138 getThreadMap() { 139 return assertExists(this.threads); 140 } 141} 142