1// Copyright (C) 2021 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 {maybeMachineLabel} from '../../base/multi_machine_trace'; 16import {PerfettoPlugin} from '../../public/plugin'; 17import {Trace} from '../../public/trace'; 18import {getThreadOrProcUri} from '../../public/utils'; 19import {NUM, NUM_NULL, STR} from '../../trace_processor/query_result'; 20import ThreadPlugin from '../dev.perfetto.Thread'; 21import {createPerfettoIndex} from '../../trace_processor/sql_utils'; 22import {uuidv4Sql} from '../../base/uuid'; 23import { 24 Config as ProcessSchedulingTrackConfig, 25 PROCESS_SCHEDULING_TRACK_KIND, 26 ProcessSchedulingTrack, 27} from './process_scheduling_track'; 28import { 29 Config as ProcessSummaryTrackConfig, 30 PROCESS_SUMMARY_TRACK, 31 ProcessSummaryTrack, 32} from './process_summary_track'; 33 34// This plugin is responsible for adding summary tracks for process and thread 35// groups. 36export default class implements PerfettoPlugin { 37 static readonly id = 'dev.perfetto.ProcessSummary'; 38 static readonly dependencies = [ThreadPlugin]; 39 40 async onTraceLoad(ctx: Trace): Promise<void> { 41 await this.addProcessTrackGroups(ctx); 42 await this.addKernelThreadSummary(ctx); 43 } 44 45 private getCpuCountByMachine(ctx: Trace): number[] { 46 const cpuCountByMachine: number[] = []; 47 for (const c of ctx.traceInfo.cpus) { 48 cpuCountByMachine[c.machine] = (cpuCountByMachine[c.machine] ?? 0) + 1; 49 } 50 return cpuCountByMachine; 51 } 52 53 private async addProcessTrackGroups(ctx: Trace): Promise<void> { 54 // Makes the queries in `ProcessSchedulingTrack` significantly faster. 55 // TODO(lalitm): figure out a better way to do this without hardcoding this 56 // here. 57 await createPerfettoIndex( 58 ctx.engine, 59 `__process_scheduling_${uuidv4Sql()}`, 60 `__intrinsic_sched_slice(utid)`, 61 ); 62 // Makes the queries in `ProcessSummaryTrack` significantly faster. 63 // TODO(lalitm): figure out a better way to do this without hardcoding this 64 // here. 65 await createPerfettoIndex( 66 ctx.engine, 67 `__process_summary_${uuidv4Sql()}`, 68 `__intrinsic_slice(track_id)`, 69 ); 70 71 const threads = ctx.plugins.getPlugin(ThreadPlugin).getThreadMap(); 72 const cpuCountByMachine = this.getCpuCountByMachine(ctx); 73 const result = await ctx.engine.query(` 74 INCLUDE PERFETTO MODULE android.process_metadata; 75 76 select * 77 from ( 78 select 79 _process_available_info_summary.upid, 80 null as utid, 81 process.pid, 82 null as tid, 83 process.name as processName, 84 null as threadName, 85 sum_running_dur > 0 as hasSched, 86 android_process_metadata.debuggable as isDebuggable, 87 case 88 when process.name = 'system_server' then 89 ifnull((select int_value from metadata where name = 'android_profile_system_server'), 0) 90 when process.name GLOB 'zygote*' then 91 ifnull((select int_value from metadata where name = 'android_profile_boot_classpath'), 0) 92 else 0 93 end as isBootImageProfiling, 94 ifnull(( 95 select group_concat(string_value) 96 from args 97 where 98 process.arg_set_id is not null and 99 arg_set_id = process.arg_set_id and 100 flat_key = 'chrome.process_label' 101 ), '') as chromeProcessLabels, 102 ifnull(machine_id, 0) as machine 103 from _process_available_info_summary 104 join process using(upid) 105 left join android_process_metadata using(upid) 106 ) 107 union all 108 select * 109 from ( 110 select 111 null, 112 utid, 113 null as pid, 114 tid, 115 null as processName, 116 thread.name threadName, 117 sum_running_dur > 0 as hasSched, 118 0 as isDebuggable, 119 0 as isBootImageProfiling, 120 '' as chromeProcessLabels, 121 ifnull(machine_id, 0) as machine 122 from _thread_available_info_summary 123 join thread using (utid) 124 where upid is null 125 ) 126 `); 127 const it = result.iter({ 128 upid: NUM_NULL, 129 utid: NUM_NULL, 130 pid: NUM_NULL, 131 tid: NUM_NULL, 132 hasSched: NUM_NULL, 133 isDebuggable: NUM_NULL, 134 isBootImageProfiling: NUM_NULL, 135 chromeProcessLabels: STR, 136 machine: NUM, 137 }); 138 for (; it.valid(); it.next()) { 139 const upid = it.upid; 140 const utid = it.utid; 141 const pid = it.pid; 142 const tid = it.tid; 143 const hasSched = Boolean(it.hasSched); 144 const isDebuggable = Boolean(it.isDebuggable); 145 const isBootImageProfiling = Boolean(it.isBootImageProfiling); 146 const subtitle = it.chromeProcessLabels; 147 const machine = it.machine; 148 149 // Group by upid if present else by utid. 150 const pidForColor = pid ?? tid ?? upid ?? utid ?? 0; 151 const uri = getThreadOrProcUri(upid, utid); 152 153 const chips: string[] = []; 154 isDebuggable && chips.push('debuggable'); 155 156 // When boot image profiling is enabled for the bootclasspath or system 157 // server, performance characteristics of the device can vary wildly. 158 // Surface that detail in the process tracks for zygote and system_server 159 // to make it clear to the user. 160 // See https://source.android.com/docs/core/runtime/boot-image-profiles 161 // for additional details. 162 isBootImageProfiling && chips.push('boot image profiling'); 163 164 const machineLabel = maybeMachineLabel(machine); 165 166 if (hasSched) { 167 const config: ProcessSchedulingTrackConfig = { 168 pidForColor, 169 upid, 170 utid, 171 }; 172 173 const cpuCount = cpuCountByMachine[machine] ?? 0; 174 ctx.tracks.registerTrack({ 175 uri, 176 title: `${upid === null ? tid : pid}${machineLabel} schedule`, 177 tags: { 178 kind: PROCESS_SCHEDULING_TRACK_KIND, 179 }, 180 chips, 181 track: new ProcessSchedulingTrack(ctx, config, cpuCount, threads), 182 subtitle, 183 }); 184 } else { 185 const config: ProcessSummaryTrackConfig = { 186 pidForColor, 187 upid, 188 utid, 189 }; 190 191 ctx.tracks.registerTrack({ 192 uri, 193 title: `${upid === null ? tid : pid}${machineLabel} summary`, 194 tags: { 195 kind: PROCESS_SUMMARY_TRACK, 196 }, 197 chips, 198 track: new ProcessSummaryTrack(ctx.engine, config), 199 subtitle, 200 }); 201 } 202 } 203 } 204 205 private async addKernelThreadSummary(ctx: Trace): Promise<void> { 206 const {engine} = ctx; 207 208 // Identify kernel threads if this is a linux system trace, and sufficient 209 // process information is available. Kernel threads are identified by being 210 // children of kthreadd (always pid 2). 211 // The query will return the kthreadd process row first, which must exist 212 // for any other kthreads to be returned by the query. 213 // TODO(rsavitski): figure out how to handle the idle process (swapper), 214 // which has pid 0 but appears as a distinct process (with its own comm) on 215 // each cpu. It'd make sense to exclude its thread state track, but still 216 // put process-scoped tracks in this group. 217 const result = await engine.query(` 218 select 219 t.utid, p.upid, (case p.pid when 2 then 1 else 0 end) isKthreadd 220 from 221 thread t 222 join process p using (upid) 223 left join process parent on (p.parent_upid = parent.upid) 224 join 225 (select true from metadata m 226 where (m.name = 'system_name' and m.str_value = 'Linux') 227 union 228 select 1 from (select true from sched limit 1)) 229 where 230 p.pid = 2 or parent.pid = 2 231 order by isKthreadd desc 232 `); 233 234 const it = result.iter({ 235 utid: NUM, 236 upid: NUM, 237 }); 238 239 // Not applying kernel thread grouping. 240 if (!it.valid()) { 241 return; 242 } 243 244 const config: ProcessSummaryTrackConfig = { 245 pidForColor: 2, 246 upid: it.upid, 247 utid: it.utid, 248 }; 249 250 ctx.tracks.registerTrack({ 251 uri: '/kernel', 252 title: `Kernel thread summary`, 253 tags: { 254 kind: PROCESS_SUMMARY_TRACK, 255 }, 256 track: new ProcessSummaryTrack(ctx.engine, config), 257 }); 258 } 259} 260