blob: 8e9af0449b49da360c4a1a9564e25071db7e6bf2 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2014 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/extras/chrome/chrome_user_friendly_category_driver.html">
<link rel="import" href="/tracing/model/helpers/chrome_process_helper.html">
<script>
'use strict';
tr.exportTo('tr.model.helpers', function() {
function ChromeRendererHelper(modelHelper, process) {
tr.model.helpers.ChromeProcessHelper.call(this, modelHelper, process);
this.mainThread_ = process.findAtMostOneThreadNamed('CrRendererMain') ||
process.findAtMostOneThreadNamed('Chrome_InProcRendererThread');
this.compositorThread_ = process.findAtMostOneThreadNamed('Compositor');
this.rasterWorkerThreads_ = process.findAllThreadsMatching(function(t) {
if (t.name === undefined) return false;
if (t.name.indexOf('CompositorTileWorker') === 0) return true;
if (t.name.indexOf('CompositorRasterWorker') === 0) return true;
return false;
});
if (!process.name)
process.name = ChromeRendererHelper.PROCESS_NAME;
}
/**
* @callback getEventAttributeCallback
* @param {tr.b.Event} event The event to read an attribute from.
* @return {number} The value of the attribute.
*/
/**
* Generate a breakdown tree from all slices of |mainThread| between
* |rangeStart| and |rangeEnd|. The callback functions |getEventStart|,
* |getEventDuration| and |getEventSelfTime| specify how to get start,
* duration and selftime from a given event.
*
* @param {tr.model.Thread} mainThread
* @param {number} rangeStart
* @param {number} rangeEnd
* @callback {getEventAttributeCallback} getEventStart
* @callback {getEventAttributeCallback} getEventDuration
* @callback {getEventAttributeCallback} getEventSelfTime
* @return {Map.<string, Object>} A time breakdown object whose keys are
* Chrome userfriendly title & values are an object that show the total spent
* between |rangeStart| & |rangeEnd|, and the list of event labels of the
* group and their total time between |rangeStart| and |rangeEnd|.
*
* Example:
* {
* layout: {
* total: 100,
* events: {'FrameView::performPreLayoutTasks': 20,..}},
* v8_runtime: {
* total: 500,
* events: {'String::NewExternalTwoByte': 0.5,..}},
* ...
* }
*/
function generateTimeBreakdownTree_(mainThread, rangeStart, rangeEnd,
getEventStart, getEventDuration, getEventSelfTime) {
if (mainThread === null) return;
var breakdownTree = {};
var range = tr.b.math.Range.fromExplicitRange(rangeStart, rangeEnd);
for (var title of
tr.e.chrome.ChromeUserFriendlyCategoryDriver.ALL_TITLES) {
breakdownTree[title] = {total: 0, events: {}};
}
for (var event of mainThread.getDescendantEvents()) {
var eventStart = getEventStart(event);
var eventDuration = getEventDuration(event);
var eventSelfTime = getEventSelfTime(event);
var eventEnd = eventStart + eventDuration;
if (!range.intersectsExplicitRangeExclusive(eventStart, eventEnd))
continue;
if (eventSelfTime === undefined) continue;
var title =
tr.e.chrome.ChromeUserFriendlyCategoryDriver.fromEvent(event);
var timeIntersectionRatio = 0;
if (eventDuration > 0) {
timeIntersectionRatio =
range.findExplicitIntersectionDuration(eventStart,
eventEnd) / eventDuration;
}
var v8Runtime = event.args['runtime-call-stat'];
if (v8Runtime !== undefined) {
var v8RuntimeObject = JSON.parse(v8Runtime);
for (var runtimeCall in v8RuntimeObject) {
// When the V8 Runtime Object contains 2 values, the 2nd value
// always represents the V8 Runtime duration.
if (v8RuntimeObject[runtimeCall].length === 2) {
if (breakdownTree['v8_runtime'].events[runtimeCall] ===
undefined) {
breakdownTree['v8_runtime'].events[runtimeCall] = 0;
}
var runtimeTime = tr.b.Unit.timestampFromUs(
v8RuntimeObject[runtimeCall][1] * timeIntersectionRatio);
breakdownTree['v8_runtime'].total += runtimeTime;
breakdownTree['v8_runtime'].events[runtimeCall] += runtimeTime;
}
}
}
// [ Slice 1 ] [ Slice 2 ] [ Slice 3 ]
// [ Slice 4 ] [ Slice 5 ]
// [ Slice 6 ] |
// | |
// | |
// v v
// start end
//
// For the case where the |start| or |end| overlapped with some existing
// slice (see above diagram), we approximate the overlapped self-time
// by multiplying the ratio of overlapped wall time to the self-time.
// There should be way to compute the exact number, but in practice,
// this should rarely happen, and when it does, the overlapped range
// is relative small so that using approximation here should be good
// enough.
var approximatedSelfTimeContribution =
eventSelfTime * timeIntersectionRatio;
breakdownTree[title].total += approximatedSelfTimeContribution;
if (breakdownTree[title].events[event.title] === undefined) {
breakdownTree[title].events[event.title] = 0;
}
breakdownTree[title].events[event.title] +=
approximatedSelfTimeContribution;
}
return breakdownTree;
}
ChromeRendererHelper.PROCESS_NAME = 'Renderer';
// Returns true if there is either a main thread or a compositor thread.
ChromeRendererHelper.isRenderProcess = function(process) {
if (process.findAtMostOneThreadNamed('CrRendererMain')) return true;
if (process.findAtMostOneThreadNamed('Compositor')) return true;
return false;
};
ChromeRendererHelper.isTracingProcess = function(process) {
return process.labels !== undefined &&
process.labels.length === 1 &&
process.labels[0] === 'chrome://tracing';
};
ChromeRendererHelper.prototype = {
__proto__: tr.model.helpers.ChromeProcessHelper.prototype,
// May be undefined.
get mainThread() {
return this.mainThread_;
},
// May be undefined.
get compositorThread() {
return this.compositorThread_;
},
// May be empty.
get rasterWorkerThreads() {
return this.rasterWorkerThreads_;
},
get isChromeTracingUI() {
return ChromeRendererHelper.isTracingProcess(this.process);
},
/**
* Generate a breakdown that attributes where wall clock time goes between
* |start| & |end| on the renderer thread.
*
* @param {number} start
* @param {number} end
* @return {Map.<string, Object>} A time breakdown map whose keys are
* Chrome userfriendly titles & values are an object that shows the total
* wall clock time spent between |start| & |end|, and the list of event
* labels of the group and their total wall clock time between |start| &
* |end|.
*
* Example:
* {
* layout: {
* total: 100,
* events: {'FrameView::performPreLayoutTasks': 20,..}},
* v8_runtime: {
* total: 500,
* events: {'String::NewExternalTwoByte': 0.5,..}},
* ...
* }
*/
generateWallClockTimeBreakdownTree: function(start, end) {
function getEventStart(e) { return e.start; }
function getEventDuration(e) { return e.duration; }
function getEventSelfTime(e) { return e.selfTime; }
var breakdownTree = generateTimeBreakdownTree_(this.mainThread,
start, end, getEventStart, getEventDuration, getEventSelfTime);
var idleTotal = end - start;
for (let cat in breakdownTree) {
idleTotal -= breakdownTree[cat].total;
}
breakdownTree['idle'] = {total: idleTotal, events: {}};
return breakdownTree;
},
/**
* Generate a breakdown that attributes where CPU time goes between
* |cpuStart| & |cpuEnd| on the renderer thread.
*
* @param {number} cpuStart
* @param {number} cpuEnd
* @return {Map.<string, Object>} A time breakdown map whose keys are
* Chrome userfriendly titles & values are an object that shows the total
* CPU time spent between |cpuStart| & |cpuEnd|, and the list of event labels
* of the group and their total CPU time between |cpuStart| & |cpuEnd|.
*
* Example:
* {
* layout: {
* total: 100,
* events: {'FrameView::performPreLayoutTasks': 20,..}},
* v8_runtime: {
* total: 500,
* events: {'String::NewExternalTwoByte': 0.5,..}},
* ...
* }
*/
generateCpuTimeBreakdownTree: function(cpuStart, cpuEnd) {
function getEventStart(e) { return e.cpuStart; }
function getEventDuration(e) { return e.cpuDuration; }
function getEventSelfTime(e) { return e.cpuSelfTime; }
return generateTimeBreakdownTree_(this.mainThread, cpuStart, cpuEnd,
getEventStart, getEventDuration, getEventSelfTime);
}
};
return {
ChromeRendererHelper,
};
});
</script>