• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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