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 {Trace} from '../../public/trace'; 16import {PerfettoPlugin} from '../../public/plugin'; 17import {getThreadOrProcUri} from '../../public/utils'; 18import {NUM, NUM_NULL, STR} from '../../trace_processor/query_result'; 19import { 20 Config as ProcessSchedulingTrackConfig, 21 PROCESS_SCHEDULING_TRACK_KIND, 22 ProcessSchedulingTrack, 23} from './process_scheduling_track'; 24import { 25 Config as ProcessSummaryTrackConfig, 26 PROCESS_SUMMARY_TRACK, 27 ProcessSummaryTrack, 28} from './process_summary_track'; 29import ThreadPlugin from '../dev.perfetto.Thread'; 30 31// This plugin is responsible for adding summary tracks for process and thread 32// groups. 33export default class implements PerfettoPlugin { 34 static readonly id = 'dev.perfetto.ProcessSummary'; 35 static readonly dependencies = [ThreadPlugin]; 36 37 async onTraceLoad(ctx: Trace): Promise<void> { 38 await this.addProcessTrackGroups(ctx); 39 await this.addKernelThreadSummary(ctx); 40 } 41 42 private async addProcessTrackGroups(ctx: Trace): Promise<void> { 43 const threads = ctx.plugins.getPlugin(ThreadPlugin).getThreadMap(); 44 45 const cpuCount = Math.max(...ctx.traceInfo.cpus, -1) + 1; 46 47 const result = await ctx.engine.query(` 48 INCLUDE PERFETTO MODULE android.process_metadata; 49 50 select * 51 from ( 52 select 53 _process_available_info_summary.upid, 54 null as utid, 55 process.pid, 56 null as tid, 57 process.name as processName, 58 null as threadName, 59 sum_running_dur > 0 as hasSched, 60 android_process_metadata.debuggable as isDebuggable, 61 ifnull(( 62 select group_concat(string_value) 63 from args 64 where 65 process.arg_set_id is not null and 66 arg_set_id = process.arg_set_id and 67 flat_key = 'chrome.process_label' 68 ), '') as chromeProcessLabels 69 from _process_available_info_summary 70 join process using(upid) 71 left join android_process_metadata using(upid) 72 ) 73 union all 74 select * 75 from ( 76 select 77 null, 78 utid, 79 null as pid, 80 tid, 81 null as processName, 82 thread.name threadName, 83 sum_running_dur > 0 as hasSched, 84 0 as isDebuggable, 85 '' as chromeProcessLabels 86 from _thread_available_info_summary 87 join thread using (utid) 88 where upid is null 89 ) 90 `); 91 92 const it = result.iter({ 93 upid: NUM_NULL, 94 utid: NUM_NULL, 95 pid: NUM_NULL, 96 tid: NUM_NULL, 97 hasSched: NUM_NULL, 98 isDebuggable: NUM_NULL, 99 chromeProcessLabels: STR, 100 }); 101 for (; it.valid(); it.next()) { 102 const upid = it.upid; 103 const utid = it.utid; 104 const pid = it.pid; 105 const tid = it.tid; 106 const hasSched = Boolean(it.hasSched); 107 const isDebuggable = Boolean(it.isDebuggable); 108 const subtitle = it.chromeProcessLabels; 109 110 // Group by upid if present else by utid. 111 const pidForColor = pid ?? tid ?? upid ?? utid ?? 0; 112 const uri = getThreadOrProcUri(upid, utid); 113 114 const chips: string[] = []; 115 isDebuggable && chips.push('debuggable'); 116 117 if (hasSched) { 118 const config: ProcessSchedulingTrackConfig = { 119 pidForColor, 120 upid, 121 utid, 122 }; 123 124 ctx.tracks.registerTrack({ 125 uri, 126 title: `${upid === null ? tid : pid} schedule`, 127 tags: { 128 kind: PROCESS_SCHEDULING_TRACK_KIND, 129 }, 130 chips, 131 track: new ProcessSchedulingTrack(ctx, config, cpuCount, threads), 132 subtitle, 133 }); 134 } else { 135 const config: ProcessSummaryTrackConfig = { 136 pidForColor, 137 upid, 138 utid, 139 }; 140 141 ctx.tracks.registerTrack({ 142 uri, 143 title: `${upid === null ? tid : pid} summary`, 144 tags: { 145 kind: PROCESS_SUMMARY_TRACK, 146 }, 147 chips, 148 track: new ProcessSummaryTrack(ctx.engine, config), 149 subtitle, 150 }); 151 } 152 } 153 } 154 155 private async addKernelThreadSummary(ctx: Trace): Promise<void> { 156 const {engine} = ctx; 157 158 // Identify kernel threads if this is a linux system trace, and sufficient 159 // process information is available. Kernel threads are identified by being 160 // children of kthreadd (always pid 2). 161 // The query will return the kthreadd process row first, which must exist 162 // for any other kthreads to be returned by the query. 163 // TODO(rsavitski): figure out how to handle the idle process (swapper), 164 // which has pid 0 but appears as a distinct process (with its own comm) on 165 // each cpu. It'd make sense to exclude its thread state track, but still 166 // put process-scoped tracks in this group. 167 const result = await engine.query(` 168 select 169 t.utid, p.upid, (case p.pid when 2 then 1 else 0 end) isKthreadd 170 from 171 thread t 172 join process p using (upid) 173 left join process parent on (p.parent_upid = parent.upid) 174 join 175 (select true from metadata m 176 where (m.name = 'system_name' and m.str_value = 'Linux') 177 union 178 select 1 from (select true from sched limit 1)) 179 where 180 p.pid = 2 or parent.pid = 2 181 order by isKthreadd desc 182 `); 183 184 const it = result.iter({ 185 utid: NUM, 186 upid: NUM, 187 }); 188 189 // Not applying kernel thread grouping. 190 if (!it.valid()) { 191 return; 192 } 193 194 const config: ProcessSummaryTrackConfig = { 195 pidForColor: 2, 196 upid: it.upid, 197 utid: it.utid, 198 }; 199 200 ctx.tracks.registerTrack({ 201 uri: '/kernel', 202 title: `Kernel thread summary`, 203 tags: { 204 kind: PROCESS_SUMMARY_TRACK, 205 }, 206 track: new ProcessSummaryTrack(ctx.engine, config), 207 }); 208 } 209} 210