blob: 47a93b01dc1452a305664a791ae2f71ff75d1dad [file] [log] [blame]
// Copyright (C) 2021 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 {v4 as uuidv4} from 'uuid';
import {Plugin, PluginContextTrace, PluginDescriptor} from '../../public';
import {
LONG_NULL,
NUM,
NUM_NULL,
STR,
STR_NULL,
} from '../../trace_processor/query_result';
import {assertExists} from '../../base/logging';
import {
Config as ProcessSchedulingTrackConfig,
PROCESS_SCHEDULING_TRACK_KIND,
ProcessSchedulingTrack,
} from './process_scheduling_track';
import {
Config as ProcessSummaryTrackConfig,
PROCESS_SUMMARY_TRACK,
ProcessSummaryTrack,
} from './process_summary_track';
// This plugin now manages both process "scheduling" and "summary" tracks.
class ProcessSummaryPlugin implements Plugin {
private upidToUuid = new Map<number, string>();
private utidToUuid = new Map<number, string>();
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
await this.addProcessTrackGroups(ctx);
await this.addKernelThreadSummary(ctx);
}
private async addProcessTrackGroups(ctx: PluginContextTrace): Promise<void> {
this.upidToUuid.clear();
this.utidToUuid.clear();
// We want to create groups of tracks in a specific order.
// The tracks should be grouped:
// by upid
// or (if upid is null) by utid
// the groups should be sorted by:
// Chrome-based process rank based on process names (e.g. Browser)
// has a heap profile or not
// total cpu time *for the whole parent process*
// process name
// upid
// thread name
// utid
const result = await ctx.engine.query(`
with candidateThreadsAndProcesses as materialized (
select upid, 0 as utid from process_track
union
select upid, 0 as utid from process_counter_track
union
select upid, utid from thread_counter_track join thread using(utid)
union
select upid, utid from thread_track join thread using(utid)
union
select upid, utid from (
select distinct utid from sched
) join thread using(utid) group by utid
union
select upid, 0 as utid from (
select distinct utid from perf_sample where callsite_id is not null
) join thread using (utid)
union
select upid, utid from (
select distinct utid from cpu_profile_stack_sample
) join thread using(utid)
union
select upid as upid, 0 as utid from heap_profile_allocation
union
select upid as upid, 0 as utid from heap_graph_object
),
schedSummary as materialized (
select
upid,
sum(thread_total_dur) as total_dur,
max(thread_max_dur) as total_max_dur,
sum(thread_event_count) as total_event_count
from (
select
utid,
sum(dur) as thread_total_dur,
max(dur) as thread_max_dur,
count() as thread_event_count
from sched where dur != -1 and utid != 0
group by utid
)
join thread using (utid)
group by upid
),
sliceSum as materialized (
select
process.upid as upid,
sum(cnt) as sliceCount
from (select track_id, count(*) as cnt from slice group by track_id)
left join thread_track on track_id = thread_track.id
left join thread on thread_track.utid = thread.utid
left join process_track on track_id = process_track.id
join process on process.upid = thread.upid
or process_track.upid = process.upid
where process.upid is not null
group by process.upid
)
select
the_tracks.upid,
the_tracks.utid,
total_dur as hasSched,
total_max_dur as schedMaxDur,
total_event_count as schedEventCount,
hasHeapProfiles,
process.pid as pid,
thread.tid as tid,
process.name as processName,
thread.name as threadName,
package_list.debuggable as isDebuggable,
ifnull((
select group_concat(string_value)
from args
where
process.arg_set_id is not null and
arg_set_id = process.arg_set_id and
flat_key = 'chrome.process_label'
), '') AS chromeProcessLabels,
(case process.name
when 'Browser' then 3
when 'Gpu' then 2
when 'Renderer' then 1
else 0
end) as chromeProcessRank
from candidateThreadsAndProcesses the_tracks
left join schedSummary using(upid)
left join (
select
distinct(upid) as upid,
true as hasHeapProfiles
from heap_profile_allocation
union
select
distinct(upid) as upid,
true as hasHeapProfiles
from heap_graph_object
) using (upid)
left join (
select
thread.upid as upid,
sum(cnt) as perfSampleCount
from (
select utid, count(*) as cnt
from perf_sample where callsite_id is not null
group by utid
) join thread using (utid)
group by thread.upid
) using (upid)
left join sliceSum using (upid)
left join thread using(utid)
left join process using(upid)
left join package_list using(uid)
order by
chromeProcessRank desc,
hasHeapProfiles desc,
perfSampleCount desc,
total_dur desc,
sliceCount desc,
processName asc nulls last,
the_tracks.upid asc nulls last,
threadName asc nulls last,
the_tracks.utid asc nulls last;
`);
const it = result.iter({
utid: NUM,
upid: NUM_NULL,
tid: NUM_NULL,
pid: NUM_NULL,
threadName: STR_NULL,
processName: STR_NULL,
hasSched: NUM_NULL,
schedMaxDur: LONG_NULL,
schedEventCount: NUM_NULL,
hasHeapProfiles: NUM_NULL,
isDebuggable: NUM_NULL,
chromeProcessLabels: STR,
});
for (; it.valid(); it.next()) {
const utid = it.utid;
const tid = it.tid;
const upid = it.upid;
const pid = it.pid;
const hasSched = Boolean(it.hasSched);
const schedMaxDur = it.schedMaxDur;
const schedEventCount = it.schedEventCount;
const isDebuggable = Boolean(it.isDebuggable);
// Group by upid if present else by utid.
let pUuid =
upid === null ? this.utidToUuid.get(utid) : this.upidToUuid.get(upid);
// These should only happen once for each track group.
if (pUuid === undefined) {
pUuid = this.getOrCreateUuid(utid, upid);
const pidForColor = pid ?? tid ?? upid ?? utid ?? 0;
const type = hasSched ? 'schedule' : 'summary';
const uri = `perfetto.ProcessScheduling#${upid}.${utid}.${type}`;
if (hasSched) {
const config: ProcessSchedulingTrackConfig = {
pidForColor,
upid,
utid,
};
ctx.registerTrack({
uri,
displayName: `${upid === null ? tid : pid} schedule`,
kind: PROCESS_SCHEDULING_TRACK_KIND,
tags: {
isDebuggable,
},
trackFactory: () =>
new ProcessSchedulingTrack(
ctx.engine,
config,
assertExists(schedMaxDur),
assertExists(schedEventCount),
),
});
} else {
const config: ProcessSummaryTrackConfig = {
pidForColor,
upid,
utid,
};
ctx.registerTrack({
uri,
displayName: `${upid === null ? tid : pid} summary`,
kind: PROCESS_SUMMARY_TRACK,
tags: {
isDebuggable,
},
trackFactory: () => new ProcessSummaryTrack(ctx.engine, config),
});
}
}
}
}
private async addKernelThreadSummary(ctx: PluginContextTrace): Promise<void> {
const {engine} = ctx;
// Identify kernel threads if this is a linux system trace, and sufficient
// process information is available. Kernel threads are identified by being
// children of kthreadd (always pid 2).
// The query will return the kthreadd process row first, which must exist
// for any other kthreads to be returned by the query.
// TODO(rsavitski): figure out how to handle the idle process (swapper),
// which has pid 0 but appears as a distinct process (with its own comm) on
// each cpu. It'd make sense to exclude its thread state track, but still
// put process-scoped tracks in this group.
const result = await engine.query(`
select
t.utid, p.upid, (case p.pid when 2 then 1 else 0 end) isKthreadd
from
thread t
join process p using (upid)
left join process parent on (p.parent_upid = parent.upid)
join
(select true from metadata m
where (m.name = 'system_name' and m.str_value = 'Linux')
union
select 1 from (select true from sched limit 1))
where
p.pid = 2 or parent.pid = 2
order by isKthreadd desc
`);
const it = result.iter({
utid: NUM,
upid: NUM,
});
// Not applying kernel thread grouping.
if (!it.valid()) {
return;
}
const config: ProcessSummaryTrackConfig = {
pidForColor: 2,
upid: it.upid,
utid: it.utid,
};
ctx.registerTrack({
uri: 'perfetto.ProcessSummary#kernel',
displayName: `Kernel thread summary`,
kind: PROCESS_SUMMARY_TRACK,
trackFactory: () => new ProcessSummaryTrack(ctx.engine, config),
});
}
private getOrCreateUuid(utid: number, upid: number | null) {
let uuid = this.getUuidUnchecked(utid, upid);
if (uuid === undefined) {
uuid = uuidv4();
if (upid === null) {
this.utidToUuid.set(utid, uuid);
} else {
this.upidToUuid.set(upid, uuid);
}
}
return uuid;
}
getUuidUnchecked(utid: number, upid: number | null) {
return upid === null
? this.utidToUuid.get(utid)
: this.upidToUuid.get(upid);
}
}
export const plugin: PluginDescriptor = {
pluginId: 'perfetto.ProcessSummary',
plugin: ProcessSummaryPlugin,
};