blob: c99c2fcabcfe6d8da9cac3e06e0f29b6a8fbf93f [file] [log] [blame]
// Copyright (C) 2020 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 * as uuidv4 from 'uuid/v4';
import {
Actions,
AddTrackArgs,
DeferredAction,
} from '../common/actions';
import {Engine} from '../common/engine';
import {
iter,
NUM,
NUM_NULL,
slowlyCountRows,
STR_NULL
} from '../common/query_iterator';
import {SCROLLING_TRACK_GROUP} from '../common/state';
import {ANDROID_LOGS_TRACK_KIND} from '../tracks/android_log/common';
import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices/common';
import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common';
import {COUNTER_TRACK_KIND} from '../tracks/counter/common';
import {CPU_FREQ_TRACK_KIND} from '../tracks/cpu_freq/common';
import {CPU_PROFILE_TRACK_KIND} from '../tracks/cpu_profile/common';
import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices/common';
import {HEAP_PROFILE_TRACK_KIND} from '../tracks/heap_profile/common';
import {
PROCESS_SCHEDULING_TRACK_KIND
} from '../tracks/process_scheduling/common';
import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary/common';
import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state/common';
interface ThreadSliceTrack {
name: string;
maxDepth: number;
trackId: number;
}
function getTrackName(args: Partial<{
name: string | null,
utid: number,
processName: string | null,
pid: number | null,
threadName: string | null,
tid: number | null,
upid: number | null,
kind: string
}>) {
const {name, upid, utid, processName, threadName, pid, tid, kind} = args;
const hasName = name !== undefined && name !== null && name !== '[NULL]';
const hasUpid = upid !== undefined && upid !== null;
const hasUtid = utid !== undefined && utid !== null;
const hasProcessName = processName !== undefined && processName !== null;
const hasThreadName = threadName !== undefined && threadName !== null;
const hasTid = tid !== undefined && tid !== null;
const hasPid = pid !== undefined && pid !== null;
const hasKind = kind !== undefined;
// If we don't have any useful information (better than
// upid/utid) we show the track kind to help with tracking
// down where this is coming from.
const kindSuffix = hasKind ? ` (${kind})` : '';
if (hasName) {
return `${name}`;
} else if (hasUpid && hasPid && hasProcessName) {
return `${processName} ${pid}`;
} else if (hasUpid && hasPid) {
return `Process ${pid}`;
} else if (hasThreadName && hasTid) {
return `${threadName} ${tid}`;
} else if (hasTid) {
return `Thread ${tid}`;
} else if (hasUpid) {
return `upid: ${upid}${kindSuffix}`;
} else if (hasUtid) {
return `utid: ${utid}${kindSuffix}`;
} else if (hasKind) {
return `${kind}`;
}
return 'Unknown';
}
export async function decideTracks(
engineId: string, engine: Engine): Promise<DeferredAction[]> {
const numGpus = await engine.getNumberOfGpus();
const tracksToAdd: AddTrackArgs[] = [];
const maxCpuFreq = await engine.query(`
select max(value)
from counter c
inner join cpu_counter_track t on c.track_id = t.id
where name = 'cpufreq';
`);
const cpus = await engine.getCpus();
for (const cpu of cpus) {
tracksToAdd.push({
engineId,
kind: CPU_SLICE_TRACK_KIND,
name: `Cpu ${cpu}`,
trackGroup: SCROLLING_TRACK_GROUP,
config: {
cpu,
}
});
}
for (const cpu of cpus) {
// Only add a cpu freq track if we have
// cpu freq data.
// TODO(taylori): Find a way to display cpu idle
// events even if there are no cpu freq events.
const cpuFreqIdle = await engine.query(`
select
id as cpu_freq_id,
(
select id
from cpu_counter_track
where name = 'cpuidle'
and cpu = ${cpu}
limit 1
) as cpu_idle_id
from cpu_counter_track
where name = 'cpufreq' and cpu = ${cpu}
limit 1;
`);
if (slowlyCountRows(cpuFreqIdle) > 0) {
const freqTrackId = +cpuFreqIdle.columns[0].longValues![0];
const idleTrackExists: boolean = !cpuFreqIdle.columns[1].isNulls![0];
const idleTrackId =
idleTrackExists ? +cpuFreqIdle.columns[1].longValues![0] : undefined;
tracksToAdd.push({
engineId,
kind: CPU_FREQ_TRACK_KIND,
name: `Cpu ${cpu} Frequency`,
trackGroup: SCROLLING_TRACK_GROUP,
config: {
cpu,
maximumValue: +maxCpuFreq.columns[0].doubleValues![0],
freqTrackId,
idleTrackId,
}
});
}
}
const rawGlobalAsyncTracks = await engine.query(`
SELECT
t.name,
t.track_ids,
MAX(experimental_slice_layout.layout_depth) as max_depth
FROM (
SELECT name, GROUP_CONCAT(track.id) AS track_ids
FROM track
WHERE track.type = "track"
GROUP BY name
) AS t CROSS JOIN experimental_slice_layout
WHERE t.track_ids = experimental_slice_layout.filter_track_ids
GROUP BY t.track_ids;
`);
for (let i = 0; i < slowlyCountRows(rawGlobalAsyncTracks); i++) {
const name = rawGlobalAsyncTracks.columns[0].stringValues![i];
const rawTrackIds = rawGlobalAsyncTracks.columns[1].stringValues![i];
const trackIds = rawTrackIds.split(',').map(v => Number(v));
const maxDepth = +rawGlobalAsyncTracks.columns[2].longValues![i];
const track = {
engineId,
kind: ASYNC_SLICE_TRACK_KIND,
trackGroup: SCROLLING_TRACK_GROUP,
name,
config: {
maxDepth,
trackIds,
},
};
tracksToAdd.push(track);
}
const upidToProcessTracks = new Map();
const rawProcessTracks = await engine.query(`
SELECT upid, name, GROUP_CONCAT(process_track.id) AS track_ids
FROM process_track
GROUP BY upid, name
`);
for (let i = 0; i < slowlyCountRows(rawProcessTracks); i++) {
const upid = +rawProcessTracks.columns[0].longValues![i];
const name = rawProcessTracks.columns[1].stringValues![i];
const rawTrackIds = rawProcessTracks.columns[2].stringValues![i];
const trackIds = rawTrackIds.split(',').map(v => Number(v));
const depthResult = await engine.query(`
SELECT MAX(layout_depth) as max_depth
FROM experimental_slice_layout('${rawTrackIds}');
`);
const maxDepth = +depthResult.columns[0].longValues![0];
const kind = ASYNC_SLICE_TRACK_KIND;
const track = {
engineId,
kind,
name,
config: {
maxDepth,
trackIds,
},
};
const tracks = upidToProcessTracks.get(upid);
if (tracks) {
tracks.push(track);
} else {
upidToProcessTracks.set(upid, [track]);
}
}
const heapProfiles = await engine.query(`
select distinct(upid) from heap_profile_allocation
union
select distinct(upid) from heap_graph_object`);
const heapUpids: Set<number> = new Set();
for (let i = 0; i < slowlyCountRows(heapProfiles); i++) {
const upid = heapProfiles.columns[0].longValues![i];
heapUpids.add(+upid);
}
const maxGpuFreq = await engine.query(`
select max(value)
from counter c
inner join gpu_counter_track t on c.track_id = t.id
where name = 'gpufreq';
`);
for (let gpu = 0; gpu < numGpus; gpu++) {
// Only add a gpu freq track if we have
// gpu freq data.
const freqExists = await engine.query(`
select id
from gpu_counter_track
where name = 'gpufreq' and gpu_id = ${gpu}
limit 1;
`);
if (slowlyCountRows(freqExists) > 0) {
tracksToAdd.push({
engineId,
kind: COUNTER_TRACK_KIND,
name: `Gpu ${gpu} Frequency`,
trackGroup: SCROLLING_TRACK_GROUP,
config: {
trackId: +freqExists.columns[0].longValues![0],
maximumValue: +maxGpuFreq.columns[0].doubleValues![0],
}
});
}
}
// Add global or GPU counter tracks that are not bound to any pid/tid.
const globalCounters = await engine.query(`
select name, id
from counter_track
where type = 'counter_track'
union
select name, id
from gpu_counter_track
where name != 'gpufreq'
`);
for (let i = 0; i < slowlyCountRows(globalCounters); i++) {
const name = globalCounters.columns[0].stringValues![i];
const trackId = +globalCounters.columns[1].longValues![i];
tracksToAdd.push({
engineId,
kind: COUNTER_TRACK_KIND,
name,
trackGroup: SCROLLING_TRACK_GROUP,
config: {
name,
trackId,
}
});
}
interface CounterTrack {
name: string;
trackId: number;
startTs?: number;
endTs?: number;
}
const counterUtids = new Map<number, CounterTrack[]>();
const threadCounters = await engine.query(`
select thread_counter_track.name, utid, thread_counter_track.id,
start_ts, end_ts from thread_counter_track join thread using(utid)
where thread_counter_track.name not in ('time_in_state')
`);
for (let i = 0; i < slowlyCountRows(threadCounters); i++) {
const name = threadCounters.columns[0].stringValues![i];
const utid = +threadCounters.columns[1].longValues![i];
const trackId = +threadCounters.columns[2].longValues![i];
let startTs = undefined;
let endTs = undefined;
if (!threadCounters.columns[3].isNulls![i]) {
startTs = +threadCounters.columns[3].longValues![i] / 1e9;
}
if (!threadCounters.columns[4].isNulls![i]) {
endTs = +threadCounters.columns[4].longValues![i] / 1e9;
}
const track: CounterTrack = {name, trackId, startTs, endTs};
const el = counterUtids.get(utid);
if (el === undefined) {
counterUtids.set(utid, [track]);
} else {
el.push(track);
}
}
const counterUpids = new Map<number, CounterTrack[]>();
const processCounters = await engine.query(`
select process_counter_track.name, upid, process_counter_track.id,
start_ts, end_ts from process_counter_track join process using(upid)
`);
for (let i = 0; i < slowlyCountRows(processCounters); i++) {
const name = processCounters.columns[0].stringValues![i];
const upid = +processCounters.columns[1].longValues![i];
const trackId = +processCounters.columns[2].longValues![i];
let startTs = undefined;
let endTs = undefined;
if (!processCounters.columns[3].isNulls![i]) {
startTs = +processCounters.columns[3].longValues![i] / 1e9;
}
if (!processCounters.columns[4].isNulls![i]) {
endTs = +processCounters.columns[4].longValues![i] / 1e9;
}
const track: CounterTrack = {name, trackId, startTs, endTs};
const el = counterUpids.get(upid);
if (el === undefined) {
counterUpids.set(upid, [track]);
} else {
el.push(track);
}
}
// Local experiments shows getting maxDepth separately is ~2x faster than
// joining with threads and processes.
const maxDepthQuery = await engine.query(`
select
thread_track.utid,
thread_track.id,
thread_track.name,
max(depth) as maxDepth
from slice
inner join thread_track on slice.track_id = thread_track.id
group by thread_track.id
`);
const utidToThreadTrack = new Map<number, ThreadSliceTrack[]>();
for (let i = 0; i < slowlyCountRows(maxDepthQuery); i++) {
const utid = maxDepthQuery.columns[0].longValues![i];
const trackId = maxDepthQuery.columns[1].longValues![i];
const name = maxDepthQuery.columns[2].stringValues![i];
const maxDepth = maxDepthQuery.columns[3].longValues![i];
const tracks = utidToThreadTrack.get(utid);
const track = {maxDepth, trackId, name};
if (tracks) {
tracks.push(track);
} else {
utidToThreadTrack.set(utid, [track]);
}
}
// For backwards compatability with older TP versions where
// android_thread_time_in_state_event table does not exists.
// TODO: remove once the track mega-query is improved.
const exists =
await engine.query(`select name from sqlite_master where type='table' and
name='android_thread_time_in_state_event'`);
if (slowlyCountRows(exists) === 0) {
await engine.query(`create view android_thread_time_in_state_event as
select null as upid, null as value where 0`);
}
// Return all threads
// sorted by:
// total cpu time *for the whole parent process*
// upid
// utid
const threadQuery = await engine.query(`
select
utid,
tid,
upid,
pid,
thread.name as threadName,
process.name as processName,
total_dur as totalDur,
ifnull(has_sched, false) as hasSched,
ifnull(has_cpu_samples, false) as hasCpuSamples
from
thread
left join (select utid, count(1), true as has_sched
from sched group by utid
) using(utid)
left join (select utid, count(1), true as has_cpu_samples
from cpu_profile_stack_sample group by utid
) using(utid)
left join process using(upid)
left join (select upid, sum(dur) as total_dur
from sched join thread using(utid)
group by upid
) using(upid)
left join (select upid, sum(value) as total_cycles
from android_thread_time_in_state_event
group by upid
) using(upid)
where utid != 0
group by utid, upid
order by total_dur desc, total_cycles desc, upid, utid`);
const upidToUuid = new Map<number, string>();
const utidToUuid = new Map<number, string>();
const addTrackGroupActions: DeferredAction[] = [];
const it = iter(
{
utid: NUM,
upid: NUM_NULL,
tid: NUM_NULL,
pid: NUM_NULL,
threadName: STR_NULL,
processName: STR_NULL,
totalDur: NUM_NULL,
hasSched: NUM,
hasCpuSamples: NUM,
},
threadQuery);
for (let i = 0; it.valid(); ++i, it.next()) {
const row = it.row;
const utid = row.utid;
const tid = row.tid;
const upid = row.upid;
const pid = row.pid;
const threadName = row.threadName;
const processName = row.processName;
const hasSchedEvents = !!row.totalDur;
const threadHasSched = !!row.hasSched;
const threadHasCpuSamples = !!row.hasCpuSamples;
const isMainThread = tid === pid;
const threadTracks =
utid === null ? undefined : utidToThreadTrack.get(utid);
if (threadTracks === undefined &&
(upid === null || counterUpids.get(upid) === undefined) &&
counterUtids.get(utid) === undefined && !threadHasSched &&
(upid === null || upid !== null && !heapUpids.has(upid))) {
continue;
}
// Group by upid if present else by utid.
let pUuid = upid === null ? utidToUuid.get(utid) : upidToUuid.get(upid);
// These should only happen once for each track group.
if (pUuid === undefined) {
pUuid = uuidv4();
const summaryTrackId = uuidv4();
if (upid === null) {
utidToUuid.set(utid, pUuid);
} else {
upidToUuid.set(upid, pUuid);
}
const pidForColor = pid || tid || upid || utid || 0;
const kind = hasSchedEvents ? PROCESS_SCHEDULING_TRACK_KIND :
PROCESS_SUMMARY_TRACK;
tracksToAdd.push({
id: summaryTrackId,
engineId,
kind,
name: `${upid === null ? tid : pid} summary`,
config: {pidForColor, upid, utid},
});
const name =
getTrackName({utid, processName, pid, threadName, tid, upid});
const addTrackGroup = Actions.addTrackGroup({
engineId,
summaryTrackId,
name,
id: pUuid,
collapsed: !(upid !== null && heapUpids.has(upid)),
});
// If the track group contains a heap profile, it should be before all
// other processes.
if (upid !== null && heapUpids.has(upid)) {
addTrackGroupActions.unshift(addTrackGroup);
} else {
addTrackGroupActions.push(addTrackGroup);
}
if (upid !== null) {
if (heapUpids.has(upid)) {
tracksToAdd.push({
engineId,
kind: HEAP_PROFILE_TRACK_KIND,
name: `Heap Profile`,
trackGroup: pUuid,
config: {upid}
});
}
const counterNames = counterUpids.get(upid);
if (counterNames !== undefined) {
counterNames.forEach(element => {
const kind = COUNTER_TRACK_KIND;
const name = getTrackName({
name: element.name,
utid,
processName,
pid,
threadName,
tid,
upid,
kind
});
tracksToAdd.push({
engineId,
kind,
name,
trackGroup: pUuid,
config: {
name,
trackId: element.trackId,
startTs: element.startTs,
endTs: element.endTs,
}
});
});
}
if (upidToProcessTracks.has(upid)) {
for (const track of upidToProcessTracks.get(upid)) {
tracksToAdd.push(Object.assign(track, {
name: getTrackName({
name: track.name,
processName,
pid,
upid,
kind: track.kind,
}),
trackGroup: pUuid,
}));
}
}
}
}
const counterThreadNames = counterUtids.get(utid);
if (counterThreadNames !== undefined) {
const kind = COUNTER_TRACK_KIND;
counterThreadNames.forEach(element => {
const name =
getTrackName({name: element.name, utid, tid, kind, threadName});
tracksToAdd.push({
engineId,
kind,
name,
trackGroup: pUuid,
config: {
name,
trackId: element.trackId,
startTs: element.startTs,
endTs: element.endTs,
}
});
});
}
if (threadHasCpuSamples) {
tracksToAdd.push({
engineId,
kind: CPU_PROFILE_TRACK_KIND,
name: `${threadName} (CPU Stack Samples)`,
trackGroup: pUuid,
config: {utid},
});
}
if (threadHasSched) {
const kind = THREAD_STATE_TRACK_KIND;
tracksToAdd.push({
engineId,
kind,
name: getTrackName({utid, tid, threadName, kind}),
trackGroup: pUuid,
isMainThread,
config: {utid}
});
}
if (threadTracks !== undefined) {
const kind = SLICE_TRACK_KIND;
for (const config of threadTracks) {
tracksToAdd.push({
engineId,
kind,
name: getTrackName({name: config.name, utid, tid, threadName, kind}),
trackGroup: pUuid,
isMainThread,
config: {maxDepth: config.maxDepth, trackId: config.trackId},
});
}
}
}
const logCount = await engine.query(`select count(1) from android_logs`);
if (logCount.columns[0].longValues![0] > 0) {
tracksToAdd.push({
engineId,
kind: ANDROID_LOGS_TRACK_KIND,
name: 'Android logs',
trackGroup: SCROLLING_TRACK_GROUP,
config: {}
});
}
const annotationSliceRows = await engine.query(`
SELECT id, name, upid FROM annotation_slice_track`);
for (let i = 0; i < slowlyCountRows(annotationSliceRows); i++) {
const id = annotationSliceRows.columns[0].longValues![i];
const name = annotationSliceRows.columns[1].stringValues![i];
const upid = annotationSliceRows.columns[2].longValues![i];
tracksToAdd.push({
engineId,
kind: SLICE_TRACK_KIND,
name,
trackGroup: upid === 0 ? SCROLLING_TRACK_GROUP : upidToUuid.get(upid),
config: {
maxDepth: 0,
namespace: 'annotation',
trackId: id,
},
});
}
const annotationCounterRows = await engine.query(`
SELECT id, name, upid, min_value, max_value
FROM annotation_counter_track`);
for (let i = 0; i < slowlyCountRows(annotationCounterRows); i++) {
const id = annotationCounterRows.columns[0].longValues![i];
const name = annotationCounterRows.columns[1].stringValues![i];
const upid = annotationCounterRows.columns[2].longValues![i];
const minimumValue = annotationCounterRows.columns[3].isNulls![i] ?
undefined :
annotationCounterRows.columns[3].doubleValues![i];
const maximumValue = annotationCounterRows.columns[4].isNulls![i] ?
undefined :
annotationCounterRows.columns[4].doubleValues![i];
tracksToAdd.push({
engineId,
kind: 'CounterTrack',
name,
trackGroup: upid === 0 ? SCROLLING_TRACK_GROUP : upidToUuid.get(upid),
config: {
name,
namespace: 'annotation',
trackId: id,
minimumValue,
maximumValue,
}
});
}
addTrackGroupActions.push(Actions.addTracks({tracks: tracksToAdd}));
return addTrackGroupActions;
}