blob: 96c1ca0295c1be72f6b21e078a0f6ddf3729a441 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright 2017 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/tracing/base/math/range.html">
<link rel="import" href="/tracing/metrics/metric_registry.html">
<link rel="import" href="/tracing/value/histogram.html">
<script>
'use strict';
tr.exportTo('tr.metrics.sh', function() {
const MAX_INPUT_EVENT_TO_STARTUP_DELAY_IN_MS = 2000;
// Post-startup activity draw delay.
const MIN_DRAW_DELAY_IN_MS = 80;
const MAX_DRAW_DELAY_IN_MS = 2000;
function findProcess(processName, model) {
for (const pid in model.processes) {
const process = model.processes[pid];
if (process.name === processName) {
return process;
}
}
return undefined;
}
function findThreads(process, threadPrefix) {
if (process === undefined) return undefined;
const threads = [];
for (const tid in process.threads) {
const thread = process.threads[tid];
if (thread.name.startsWith(threadPrefix)) {
threads.push(thread);
}
}
return threads;
}
function findUIThread(process) {
if (process === undefined) return undefined;
const threads = findThreads(process, 'UI Thread');
if (threads !== undefined && threads.length === 1) {
return threads[0];
}
return process.threads[process.pid];
}
// Returns slices with actual app's process startup, excluding other delays.
function findLaunchSlices(model) {
const launches = [];
const binders = findThreads(findProcess('system_server', model), 'Binder');
for (const binderId in binders) {
const binder = binders[binderId];
for (const sliceId in binder.asyncSliceGroup.slices) {
const slice = binder.asyncSliceGroup.slices[sliceId];
if (slice.title.startsWith('launching:')) {
launches.push(slice);
}
}
}
return launches;
}
// Try to find draw event when activity just shown.
function findDrawSlice(appName, startNotBefore, model) {
let drawSlice = undefined;
const thread = findUIThread(findProcess(appName, model));
if (thread === undefined) return undefined;
for (const sliceId in thread.sliceGroup.slices) {
const slice = thread.sliceGroup.slices[sliceId];
if (slice.start < startNotBefore + MIN_DRAW_DELAY_IN_MS ||
slice.start > startNotBefore + MAX_DRAW_DELAY_IN_MS) continue;
if (slice.title !== 'draw') continue;
// TODO(kraynov): Add reportFullyDrawn() support.
if (drawSlice === undefined || slice.start < drawSlice.start) {
drawSlice = slice;
}
}
return drawSlice;
}
// Try to find input event before a process starts.
function findInputEventSlice(endNotAfter, model) {
const endNotBefore = endNotAfter - MAX_INPUT_EVENT_TO_STARTUP_DELAY_IN_MS;
let inputSlice = undefined;
const systemUi = findUIThread(findProcess('com.android.systemui', model));
if (systemUi === undefined) return undefined;
for (const sliceId in systemUi.asyncSliceGroup.slices) {
const slice = systemUi.asyncSliceGroup.slices[sliceId];
if (slice.end > endNotAfter || slice.end < endNotBefore) continue;
if (slice.title !== 'deliverInputEvent') continue;
if (inputSlice === undefined || slice.end > inputSlice.end) {
inputSlice = slice;
}
}
return inputSlice;
}
function computeStartupTimeInMs(appName, launchSlice, model) {
let startupStart = launchSlice.start;
let startupEnd = launchSlice.end;
const drawSlice = findDrawSlice(appName, launchSlice.end, model);
if (drawSlice !== undefined) {
startupEnd = drawSlice.end;
}
const inputSlice = findInputEventSlice(launchSlice.start, model);
if (inputSlice !== undefined) {
startupStart = inputSlice.start;
}
return startupEnd - startupStart;
}
// App startup time metric.
function measureStartup(histograms, model) {
const launches = findLaunchSlices(model);
for (const sliceId in launches) {
const launchSlice = launches[sliceId];
const appName = launchSlice.title.split(': ')[1];
const startupMs = computeStartupTimeInMs(appName, launchSlice, model);
histograms.createHistogram(`android:systrace:startup:${appName}`,
tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, startupMs);
}
}
// Metric which measures time spent by process threads in each thread state.
// The value of metric is a time percentage relative to the length of selected
// range of interest.
function measureThreadStates(histograms, model, rangeOfInterest) {
for (const pid in model.processes) {
const process = model.processes[pid];
if (process.name === undefined) continue;
let hasSlices = false;
let timeRunning = 0;
let timeRunnable = 0;
let timeSleeping = 0;
let timeUninterruptible = 0;
let timeBlockIO = 0;
let timeUnknown = 0;
for (const tid in process.threads) {
const thread = process.threads[tid];
if (thread.timeSlices === undefined) continue;
for (const sliceId in thread.timeSlices) {
const slice = thread.timeSlices[sliceId];
const sliceRange =
tr.b.math.Range.fromExplicitRange(slice.start, slice.end);
const intersection = rangeOfInterest.findIntersection(sliceRange);
const duration = intersection.duration;
if (duration === 0) continue;
hasSlices = true;
if (slice.title === 'Running') {
timeRunning += duration;
} else if (slice.title === 'Runnable') {
timeRunnable += duration;
} else if (slice.title === 'Sleeping') {
timeSleeping += duration;
} else if (slice.title.startsWith('Uninterruptible')) {
timeUninterruptible += duration;
if (slice.title.includes('Block I/O')) timeBlockIO += duration;
} else {
timeUnknown += duration;
}
}
}
if (hasSlices) {
// For sake of simplicity we don't count wall time for each
// thread/process and just calculate relative values against selected
// range of interest.
const wall = rangeOfInterest.max - rangeOfInterest.min;
histograms.createHistogram(
`android:systrace:threadtime:${process.name}:running`,
tr.b.Unit.byName.normalizedPercentage, timeRunning / wall);
histograms.createHistogram(
`android:systrace:threadtime:${process.name}:runnable`,
tr.b.Unit.byName.normalizedPercentage, timeRunnable / wall);
histograms.createHistogram(
`android:systrace:threadtime:${process.name}:sleeping`,
tr.b.Unit.byName.normalizedPercentage, timeSleeping / wall);
histograms.createHistogram(
`android:systrace:threadtime:${process.name}:blockio`,
tr.b.Unit.byName.normalizedPercentage, timeBlockIO / wall);
histograms.createHistogram(
`android:systrace:threadtime:${process.name}:uninterruptible`,
tr.b.Unit.byName.normalizedPercentage, timeUninterruptible / wall);
// In case of changing names in systrace and importer.
if (timeUnknown > 0) {
histograms.createHistogram(
`android:systrace:threadtime:${process.name}:unknown`,
tr.b.Unit.byName.normalizedPercentage, timeUnknown / wall);
}
}
}
}
function androidSystraceMetric(histograms, model, options) {
let rangeOfInterest = model.bounds;
if (options !== undefined && options.rangeOfInterest !== undefined) {
rangeOfInterest = options.rangeOfInterest;
}
measureStartup(histograms, model);
measureThreadStates(histograms, model, rangeOfInterest);
}
tr.metrics.MetricRegistry.register(androidSystraceMetric, {
supportsRangeOfInterest: true
});
return {
androidSystraceMetric,
};
});
</script>