blob: 2ee0e452bf77e4d2b2783ec27fe444277674bce4 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2015 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/base.html">
<link rel="import" href="/tracing/base/range_utils.html">
<link rel="import" href="/tracing/core/auditor.html">
<link rel="import" href="/tracing/extras/chrome/cc/input_latency_async_slice.html">
<link rel="import" href="/tracing/importer/find_input_expectations.html">
<link rel="import" href="/tracing/importer/find_load_expectations.html">
<link rel="import" href="/tracing/importer/find_startup_expectations.html">
<link rel="import" href="/tracing/model/event_info.html">
<link rel="import" href="/tracing/model/ir_coverage.html">
<link rel="import" href="/tracing/model/user_model/idle_expectation.html">
<script>
'use strict';
tr.exportTo('tr.importer', function() {
var INSIGNIFICANT_MS = 1;
function UserModelBuilder(model) {
this.model = model;
this.modelHelper = model.getOrCreateHelper(
tr.model.helpers.ChromeModelHelper);
};
UserModelBuilder.supportsModelHelper = function(modelHelper) {
return modelHelper.browserHelper !== undefined;
};
UserModelBuilder.prototype = {
buildUserModel: function() {
if (!this.modelHelper || !this.modelHelper.browserHelper)
return;
var expectations = undefined;
try {
expectations = this.findUserExpectations();
// There are not currently any known cases when this could throw.
} catch (error) {
this.model.importWarning({
type: 'UserModelBuilder',
message: error,
showToUser: true
});
return;
}
expectations.forEach(function(expectation) {
this.model.userModel.expectations.push(expectation);
}, this);
// TODO(benjhayden) Find Gestures here.
},
findUserExpectations: function() {
var expectations = [];
expectations.push.apply(expectations, tr.importer.findStartupExpectations(
this.modelHelper));
expectations.push.apply(expectations, tr.importer.findLoadExpectations(
this.modelHelper));
expectations.push.apply(expectations, tr.importer.findInputExpectations(
this.modelHelper));
// findIdleExpectations must be called last!
expectations.push.apply(
expectations, this.findIdleExpectations(expectations));
this.collectUnassociatedEvents_(expectations);
return expectations;
},
// Find all unassociated top-level ThreadSlices. If they start during an
// Idle or Load IR, then add their entire hierarchy to that IR.
collectUnassociatedEvents_: function(rirs) {
var vacuumIRs = [];
rirs.forEach(function(ir) {
if (ir instanceof tr.model.um.IdleExpectation ||
ir instanceof tr.model.um.LoadExpectation ||
ir instanceof tr.model.um.StartupExpectation)
vacuumIRs.push(ir);
});
if (vacuumIRs.length === 0)
return;
var allAssociatedEvents = tr.model.getAssociatedEvents(rirs);
var unassociatedEvents = tr.model.getUnassociatedEvents(
this.model, allAssociatedEvents);
unassociatedEvents.forEach(function(event) {
if (!(event instanceof tr.model.ThreadSlice))
return;
if (!event.isTopLevel)
return;
for (var iri = 0; iri < vacuumIRs.length; ++iri) {
var ir = vacuumIRs[iri];
if ((event.start >= ir.start) &&
(event.start < ir.end)) {
ir.associatedEvents.addEventSet(event.entireHierarchy);
return;
}
}
});
},
// Fill in the empty space between IRs with IdleIRs.
findIdleExpectations: function(otherIRs) {
if (this.model.bounds.isEmpty)
return;
var emptyRanges = tr.b.findEmptyRangesBetweenRanges(
tr.b.convertEventsToRanges(otherIRs),
this.model.bounds);
var irs = [];
var model = this.model;
emptyRanges.forEach(function(range) {
// Ignore insignificantly tiny idle ranges.
if (range.max < (range.min + INSIGNIFICANT_MS))
return;
irs.push(new tr.model.um.IdleExpectation(
model, range.min, range.max - range.min));
});
return irs;
}
};
function createCustomizeModelLinesFromModel(model) {
var modelLines = [];
modelLines.push(' audits.addEvent(model.browserMain,');
modelLines.push(' {title: \'model start\', start: 0, end: 1});');
var typeNames = {};
for (var typeName in tr.e.cc.INPUT_EVENT_TYPE_NAMES) {
typeNames[tr.e.cc.INPUT_EVENT_TYPE_NAMES[typeName]] = typeName;
}
var modelEvents = new tr.model.EventSet();
model.userModel.expectations.forEach(function(ir, index) {
modelEvents.addEventSet(ir.sourceEvents);
});
modelEvents = modelEvents.toArray();
modelEvents.sort(tr.importer.compareEvents);
modelEvents.forEach(function(event) {
var startAndEnd = 'start: ' + parseInt(event.start) + ', ' +
'end: ' + parseInt(event.end) + '});';
if (event instanceof tr.e.cc.InputLatencyAsyncSlice) {
modelLines.push(' audits.addInputEvent(model, INPUT_TYPE.' +
typeNames[event.typeName] + ',');
} else if (event.title === 'RenderFrameImpl::didCommitProvisionalLoad') {
modelLines.push(' audits.addCommitLoadEvent(model,');
} else if (event.title ===
'InputHandlerProxy::HandleGestureFling::started') {
modelLines.push(' audits.addFlingAnimationEvent(model,');
} else if (event.title === tr.model.helpers.IMPL_RENDERING_STATS) {
modelLines.push(' audits.addFrameEvent(model,');
} else if (event.title === tr.importer.CSS_ANIMATION_TITLE) {
modelLines.push(' audits.addEvent(model.rendererMain, {');
modelLines.push(' title: \'Animation\', ' + startAndEnd);
return;
} else {
throw ('You must extend createCustomizeModelLinesFromModel()' +
'to support this event:\n' + event.title + '\n');
}
modelLines.push(' {' + startAndEnd);
});
modelLines.push(' audits.addEvent(model.browserMain,');
modelLines.push(' {' +
'title: \'model end\', ' +
'start: ' + (parseInt(model.bounds.max) - 1) + ', ' +
'end: ' + parseInt(model.bounds.max) + '});');
return modelLines;
}
function createExpectedIRLinesFromModel(model) {
var expectedLines = [];
var irCount = model.userModel.expectations.length;
model.userModel.expectations.forEach(function(ir, index) {
var irString = ' {';
irString += 'title: \'' + ir.title + '\', ';
irString += 'start: ' + parseInt(ir.start) + ', ';
irString += 'end: ' + parseInt(ir.end) + ', ';
irString += 'eventCount: ' + ir.sourceEvents.length;
irString += '}';
if (index < (irCount - 1))
irString += ',';
expectedLines.push(irString);
});
return expectedLines;
}
function createIRFinderTestCaseStringFromModel(model) {
var filename = window.location.hash.substr(1);
var testName = filename.substr(filename.lastIndexOf('/') + 1);
testName = testName.substr(0, testName.indexOf('.'));
// createCustomizeModelLinesFromModel() throws an error if there's an
// unsupported event.
try {
var testLines = [];
testLines.push(' /*');
testLines.push(' This test was generated from');
testLines.push(' ' + filename + '');
testLines.push(' */');
testLines.push(' test(\'' + testName + '\', function() {');
testLines.push(' var verifier = new UserExpectationVerifier();');
testLines.push(' verifier.customizeModelCallback = function(model) {');
testLines.push.apply(testLines,
createCustomizeModelLinesFromModel(model));
testLines.push(' };');
testLines.push(' verifier.expectedIRs = [');
testLines.push.apply(testLines, createExpectedIRLinesFromModel(model));
testLines.push(' ];');
testLines.push(' verifier.verify();');
testLines.push(' });');
return testLines.join('\n');
} catch (error) {
return error;
}
}
return {
UserModelBuilder: UserModelBuilder,
createIRFinderTestCaseStringFromModel: createIRFinderTestCaseStringFromModel
};
});
</script>