1<!DOCTYPE html> 2<!-- 3Copyright (c) 2015 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/tracing/base/base.html"> 9<link rel="import" href="/tracing/base/range_utils.html"> 10<link rel="import" href="/tracing/core/auditor.html"> 11<link rel="import" href="/tracing/extras/chrome/cc/input_latency_async_slice.html"> 12<link rel="import" href="/tracing/importer/find_input_expectations.html"> 13<link rel="import" href="/tracing/importer/find_load_expectations.html"> 14<link rel="import" href="/tracing/model/event_info.html"> 15<link rel="import" href="/tracing/model/ir_coverage.html"> 16<link rel="import" href="/tracing/model/user_model/idle_expectation.html"> 17 18<script> 19'use strict'; 20 21tr.exportTo('tr.importer', function() { 22 var INSIGNIFICANT_MS = 1; 23 24 function UserModelBuilder(model) { 25 this.model = model; 26 this.modelHelper = model.getOrCreateHelper( 27 tr.model.helpers.ChromeModelHelper); 28 }; 29 30 UserModelBuilder.supportsModelHelper = function(modelHelper) { 31 return modelHelper.browserHelper !== undefined; 32 }; 33 34 UserModelBuilder.prototype = { 35 buildUserModel: function() { 36 if (!this.modelHelper || !this.modelHelper.browserHelper) 37 return; 38 39 var expectations = undefined; 40 try { 41 expectations = this.findUserExpectations(); 42 // There are not currently any known cases when this could throw. 43 } catch (error) { 44 this.model.importWarning({ 45 type: 'UserModelBuilder', 46 message: error, 47 showToUser: true 48 }); 49 return; 50 } 51 expectations.forEach(function(expectation) { 52 this.model.userModel.expectations.push(expectation); 53 }, this); 54 55 // TODO(benjhayden) Find Gestures here. 56 }, 57 58 findUserExpectations: function() { 59 var expectations = []; 60 expectations.push.apply(expectations, tr.importer.findLoadExpectations( 61 this.modelHelper)); 62 expectations.push.apply(expectations, tr.importer.findInputExpectations( 63 this.modelHelper)); 64 // findIdleExpectations must be called last! 65 expectations.push.apply( 66 expectations, this.findIdleExpectations(expectations)); 67 this.collectUnassociatedEvents_(expectations); 68 return expectations; 69 }, 70 71 // Find all unassociated top-level ThreadSlices. If they start during an 72 // Idle or Load IR, then add their entire hierarchy to that IR. 73 collectUnassociatedEvents_: function(rirs) { 74 var vacuumIRs = []; 75 rirs.forEach(function(ir) { 76 if (ir instanceof tr.model.um.LoadExpectation || 77 ir instanceof tr.model.um.IdleExpectation) 78 vacuumIRs.push(ir); 79 }); 80 if (vacuumIRs.length === 0) 81 return; 82 83 var allAssociatedEvents = tr.model.getAssociatedEvents(rirs); 84 var unassociatedEvents = tr.model.getUnassociatedEvents( 85 this.model, allAssociatedEvents); 86 87 unassociatedEvents.forEach(function(event) { 88 if (!(event instanceof tr.model.ThreadSlice)) 89 return; 90 91 if (!event.isTopLevel) 92 return; 93 94 for (var iri = 0; iri < vacuumIRs.length; ++iri) { 95 var ir = vacuumIRs[iri]; 96 97 if ((event.start >= ir.start) && 98 (event.start < ir.end)) { 99 ir.associatedEvents.addEventSet(event.entireHierarchy); 100 return; 101 } 102 } 103 }); 104 }, 105 106 // Fill in the empty space between IRs with IdleIRs. 107 findIdleExpectations: function(otherIRs) { 108 if (this.model.bounds.isEmpty) 109 return; 110 var emptyRanges = tr.b.findEmptyRangesBetweenRanges( 111 tr.b.convertEventsToRanges(otherIRs), 112 this.model.bounds); 113 var irs = []; 114 var model = this.model; 115 emptyRanges.forEach(function(range) { 116 // Ignore insignificantly tiny idle ranges. 117 if (range.max < (range.min + INSIGNIFICANT_MS)) 118 return; 119 irs.push(new tr.model.um.IdleExpectation( 120 model, range.min, range.max - range.min)); 121 }); 122 return irs; 123 } 124 }; 125 126 function createCustomizeModelLinesFromModel(model) { 127 var modelLines = []; 128 modelLines.push(' audits.addEvent(model.browserMain,'); 129 modelLines.push(' {title: \'model start\', start: 0, end: 1});'); 130 131 var typeNames = {}; 132 for (var typeName in tr.e.cc.INPUT_EVENT_TYPE_NAMES) { 133 typeNames[tr.e.cc.INPUT_EVENT_TYPE_NAMES[typeName]] = typeName; 134 } 135 136 var modelEvents = new tr.model.EventSet(); 137 model.userModel.expectations.forEach(function(ir, index) { 138 modelEvents.addEventSet(ir.sourceEvents); 139 }); 140 modelEvents = modelEvents.toArray(); 141 modelEvents.sort(tr.importer.compareEvents); 142 143 modelEvents.forEach(function(event) { 144 var startAndEnd = 'start: ' + parseInt(event.start) + ', ' + 145 'end: ' + parseInt(event.end) + '});'; 146 if (event instanceof tr.e.cc.InputLatencyAsyncSlice) { 147 modelLines.push(' audits.addInputEvent(model, INPUT_TYPE.' + 148 typeNames[event.typeName] + ','); 149 } else if (event.title === 'RenderFrameImpl::didCommitProvisionalLoad') { 150 modelLines.push(' audits.addCommitLoadEvent(model,'); 151 } else if (event.title === 152 'InputHandlerProxy::HandleGestureFling::started') { 153 modelLines.push(' audits.addFlingAnimationEvent(model,'); 154 } else if (event.title === tr.model.helpers.IMPL_RENDERING_STATS) { 155 modelLines.push(' audits.addFrameEvent(model,'); 156 } else if (event.title === tr.importer.CSS_ANIMATION_TITLE) { 157 modelLines.push(' audits.addEvent(model.rendererMain, {'); 158 modelLines.push(' title: \'Animation\', ' + startAndEnd); 159 return; 160 } else { 161 throw ('You must extend createCustomizeModelLinesFromModel()' + 162 'to support this event:\n' + event.title + '\n'); 163 } 164 modelLines.push(' {' + startAndEnd); 165 }); 166 167 modelLines.push(' audits.addEvent(model.browserMain,'); 168 modelLines.push(' {' + 169 'title: \'model end\', ' + 170 'start: ' + (parseInt(model.bounds.max) - 1) + ', ' + 171 'end: ' + parseInt(model.bounds.max) + '});'); 172 return modelLines; 173 } 174 175 function createExpectedIRLinesFromModel(model) { 176 var expectedLines = []; 177 var irCount = model.userModel.expectations.length; 178 model.userModel.expectations.forEach(function(ir, index) { 179 var irString = ' {'; 180 irString += 'title: \'' + ir.title + '\', '; 181 irString += 'start: ' + parseInt(ir.start) + ', '; 182 irString += 'end: ' + parseInt(ir.end) + ', '; 183 irString += 'eventCount: ' + ir.sourceEvents.length; 184 irString += '}'; 185 if (index < (irCount - 1)) 186 irString += ','; 187 expectedLines.push(irString); 188 }); 189 return expectedLines; 190 } 191 192 function createIRFinderTestCaseStringFromModel(model) { 193 var filename = window.location.hash.substr(1); 194 var testName = filename.substr(filename.lastIndexOf('/') + 1); 195 testName = testName.substr(0, testName.indexOf('.')); 196 197 // createCustomizeModelLinesFromModel() throws an error if there's an 198 // unsupported event. 199 try { 200 var testLines = []; 201 testLines.push(' /*'); 202 testLines.push(' This test was generated from'); 203 testLines.push(' ' + filename + ''); 204 testLines.push(' */'); 205 testLines.push(' test(\'' + testName + '\', function() {'); 206 testLines.push(' var verifier = new UserExpectationVerifier();'); 207 testLines.push(' verifier.customizeModelCallback = function(model) {'); 208 testLines.push.apply(testLines, 209 createCustomizeModelLinesFromModel(model)); 210 testLines.push(' };'); 211 testLines.push(' verifier.expectedIRs = ['); 212 testLines.push.apply(testLines, createExpectedIRLinesFromModel(model)); 213 testLines.push(' ];'); 214 testLines.push(' verifier.verify();'); 215 testLines.push(' });'); 216 return testLines.join('\n'); 217 } catch (error) { 218 return error; 219 } 220 } 221 222 return { 223 UserModelBuilder: UserModelBuilder, 224 createIRFinderTestCaseStringFromModel: createIRFinderTestCaseStringFromModel 225 }; 226}); 227</script> 228