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