android_auditor_test.html revision 4a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724
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/core/test_utils.html">
9<link rel="import" href="/tracing/model/frame.html">
10<link rel="import" href="/tracing/extras/android/android_auditor.html">
11<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
12
13<script>
14'use strict';
15
16tr.b.unittest.testSuite(function() {
17  var SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
18  var newSliceEx = tr.c.test_utils.newSliceEx;
19  var FRAME_PERF_CLASS = tr.model.FRAME_PERF_CLASS;
20  var newThreadSlice = tr.c.test_utils.newThreadSlice;
21  var TimeDuration = tr.b.units.TimeDuration;
22
23  test('saveLayerAlert_badAlpha', function() {
24    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
25      var renderThread = model.getOrCreateProcess(1).getOrCreateThread(2);
26      renderThread.name = 'RenderThread';
27      renderThread.sliceGroup.pushSlice(newSliceEx(
28          {title: 'doFrame', start: 200, duration: 5}));
29      renderThread.sliceGroup.pushSlice(newSliceEx({
30          title: 'BadAlphaView alpha caused saveLayer 480x320',
31          start: 203,
32          duration: 1
33      }));
34
35      // doesn't create alert, since bad alpha accounts for this savelayer
36      renderThread.sliceGroup.pushSlice(newSliceEx(
37          {title: 'unclipped saveLayer 480x320', start: 204, duration: 1}));
38    }, tr.e.audits.AndroidAuditor);
39
40    assert.equal(model.alerts.length, 1);
41
42    var alert = model.alerts[0];
43    assert.equal(alert.args['view name'], 'BadAlphaView');
44    assert.equal(alert.args.width, 480);
45    assert.equal(alert.args.height, 320);
46  });
47
48  test('saveLayerAlert_canvas', function() {
49    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
50      var renderThread = model.getOrCreateProcess(1).getOrCreateThread(2);
51      renderThread.name = 'RenderThread';
52      renderThread.sliceGroup.pushSlice(newSliceEx(
53          {title: 'doFrame', start: 200, duration: 5}));
54      renderThread.sliceGroup.pushSlice(newSliceEx(
55          {title: 'saveLayer 480x320', start: 204, duration: 1}));
56    }, tr.e.audits.AndroidAuditor);
57
58    assert.equal(model.alerts.length, 1);
59
60    var alert = model.alerts[0];
61    assert.equal(alert.args['Clipped saveLayer count'], 1);
62    assert.equal(alert.associatedEvents.length, 2);
63  });
64
65  test('generatePathAlert', function() {
66    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
67      var renderThread = model.getOrCreateProcess(1).getOrCreateThread(2);
68      renderThread.name = 'RenderThread';
69      renderThread.sliceGroup.pushSlice(newSliceEx(
70          {title: 'doFrame', start: 0, duration: 20}));
71      renderThread.sliceGroup.pushSlice(newSliceEx(
72          {title: 'Generate Path Texture', start: 0, duration: 3}));
73      renderThread.sliceGroup.pushSlice(newSliceEx(
74          {title: 'Generate Path Texture', start: 3, duration: 6}));
75    }, tr.e.audits.AndroidAuditor);
76
77    assert.equal(model.alerts.length, 1);
78
79    var alert = model.alerts[0];
80    assert.deepEqual(alert.args['Time spent'], new TimeDuration(9));
81    assert.equal(alert.associatedEvents.length, 3);
82  });
83
84  test('uploadAlert', function() {
85    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
86      var renderThread = model.getOrCreateProcess(1).getOrCreateThread(2);
87      renderThread.name = 'RenderThread';
88      renderThread.sliceGroup.pushSlice(newSliceEx(
89          {title: 'doFrame', start: 0, duration: 20}));
90      renderThread.sliceGroup.pushSlice(newSliceEx(
91          {title: 'Upload 1000x1000 Texture', start: 0, duration: 15}));
92    }, tr.e.audits.AndroidAuditor);
93
94    assert.equal(model.alerts.length, 1);
95
96    var alert = model.alerts[0];
97    assert.equal(alert.args['Pixels uploaded'], '1.00 million');
98    assert.deepEqual(alert.args['Time spent'], new TimeDuration(15));
99    assert.equal(alert.associatedEvents.length, 2);
100  });
101
102  test('listViewAlert', function() {
103    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
104      var uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
105      uiThread.sliceGroup.pushSlice(newSliceEx(
106          {title: 'obtainView', start: 0, duration: 5}));
107      uiThread.sliceGroup.pushSlice(newSliceEx(
108          {title: 'setupListItem', start: 5, duration: 5}));
109      uiThread.sliceGroup.pushSlice(newSliceEx(
110          {title: 'obtainView', start: 10, duration: 5}));
111      uiThread.sliceGroup.pushSlice(newSliceEx(
112          {title: 'setupListItem', start: 15, duration: 5}));
113      uiThread.sliceGroup.pushSlice(newSliceEx(
114          {title: 'performTraversals', start: 20, duration: 5}));
115
116      // short frame, so no alert should be generated
117      uiThread.sliceGroup.pushSlice(newSliceEx(
118          {title: 'obtainView', start: 50, duration: 5}));
119      uiThread.sliceGroup.pushSlice(newSliceEx(
120          {title: 'setupListItem', start: 55, duration: 5}));
121      uiThread.sliceGroup.pushSlice(newSliceEx(
122          {title: 'performTraversals', start: 60, duration: 1}));
123
124      uiThread.sliceGroup.pushSlice(newSliceEx(
125          {title: 'obtainView', start: 100, duration: 10}));
126      uiThread.sliceGroup.pushSlice(newSliceEx(
127          {title: 'inflate', start: 101, duration: 8}));
128      uiThread.sliceGroup.pushSlice(newSliceEx(
129          {title: 'setupListItem', start: 110, duration: 10}));
130      uiThread.sliceGroup.pushSlice(newSliceEx(
131          {title: 'performTraversals', start: 120, duration: 5}));
132    }, tr.e.audits.AndroidAuditor);
133
134    assert.equal(model.alerts.length, 2);
135    var alert = model.alerts[0];
136    assert.equal(alert.args['ListView items rebound'], 2);
137    assert.deepEqual(alert.args['Time spent'], new TimeDuration(20));
138    assert.equal(alert.associatedEvents.length, 5);
139
140    var alert = model.alerts[1];
141    assert.equal(alert.args['ListView items inflated'], 1);
142    assert.deepEqual(alert.args['Time spent'], new TimeDuration(20));
143    assert.equal(alert.associatedEvents.length, 3); // note: inflate not assoc.
144  });
145
146  test('measureLayoutAlert', function() {
147    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
148      var uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
149      uiThread.sliceGroup.pushSlice(newSliceEx(
150          {title: 'performTraversals', start: 0, duration: 20}));
151      uiThread.sliceGroup.pushSlice(newSliceEx(
152          {title: 'measure', start: 0, duration: 5}));
153      uiThread.sliceGroup.pushSlice(newSliceEx(
154          {title: 'layout', start: 10, duration: 5}));
155    }, tr.e.audits.AndroidAuditor);
156
157    assert.equal(model.alerts.length, 1);
158
159    var alert = model.alerts[0];
160    assert.deepEqual(alert.args['Time spent'], new TimeDuration(10));
161    assert.equal(alert.associatedEvents.length, 3);
162  });
163
164  test('viewDrawAlert', function() {
165    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
166      var uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
167      // modern naming
168      uiThread.sliceGroup.pushSlice(newSliceEx(
169          {title: 'performTraversals', start: 0, duration: 20}));
170      uiThread.sliceGroup.pushSlice(newSliceEx(
171          {title: 'Record View#draw()', start: 0, duration: 10}));
172
173      // legacy naming
174      uiThread.sliceGroup.pushSlice(newSliceEx(
175          {title: 'performTraversals', start: 40, duration: 20}));
176      uiThread.sliceGroup.pushSlice(newSliceEx(
177          {title: 'getDisplayList', start: 40, duration: 10}));
178    }, tr.e.audits.AndroidAuditor);
179
180    assert.equal(model.alerts.length, 2);
181    assert.deepEqual(model.alerts[0].args['Time spent'], new TimeDuration(10));
182    assert.deepEqual(model.alerts[1].args['Time spent'], new TimeDuration(10));
183  });
184
185  test('blockingGcAlert', function() {
186    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
187      var uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
188      var sliceGroup = uiThread.sliceGroup;
189      sliceGroup.pushSlice(newSliceEx(
190          {title: 'performTraversals', start: 0, duration: 20}));
191      sliceGroup.pushSlice(newSliceEx(
192          {title: 'DVM Suspend', start: 0, duration: 15}));
193
194      sliceGroup.pushSlice(newSliceEx(
195          {title: 'performTraversals', start: 50, duration: 20}));
196      sliceGroup.pushSlice(newSliceEx(
197          {title: 'GC: Wait For Concurrent', start: 50, duration: 15}));
198    }, tr.e.audits.AndroidAuditor);
199
200    assert.equal(model.alerts.length, 2);
201    assert.deepEqual(model.alerts[0].args['Blocked duration'],
202        new TimeDuration(15));
203    assert.deepEqual(model.alerts[1].args['Blocked duration'],
204        new TimeDuration(15));
205  });
206
207  test('lockContentionAlert', function() {
208    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
209      var uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
210      var sliceGroup = uiThread.sliceGroup;
211      sliceGroup.pushSlice(newSliceEx(
212          {title: 'performTraversals', start: 0, duration: 20}));
213      sliceGroup.pushSlice(newSliceEx(
214          {title: 'Lock Contention on a lock', start: 0, duration: 15}));
215    }, tr.e.audits.AndroidAuditor);
216
217    assert.equal(model.alerts.length, 1);
218    assert.deepEqual(model.alerts[0].args['Blocked duration'],
219        new TimeDuration(15));
220  });
221
222  test('schedulingAlerts', function() {
223    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
224      var uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
225      uiThread.sliceGroup.pushSlice(newSliceEx(
226          {title: 'performTraversals', start: 0, duration: 20}));
227      uiThread.timeSlices = [
228          newThreadSlice(uiThread, SCHEDULING_STATE.RUNNING, 0, 6),
229          newThreadSlice(uiThread, SCHEDULING_STATE.RUNNABLE, 6, 10),
230          newThreadSlice(uiThread, SCHEDULING_STATE.RUNNING, 16, 4)];
231    }, tr.e.audits.AndroidAuditor);
232    assert.equal(model.alerts.length, 1);
233    var alert = model.alerts[0];
234    assert.equal(alert.info.title, 'Scheduling delay');
235    assert.deepEqual(alert.args['Not scheduled, but runnable'],
236        new TimeDuration(10));
237
238    model = tr.c.test_utils.newModelWithAuditor(function(model) {
239      var uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
240      uiThread.sliceGroup.pushSlice(newSliceEx(
241          {title: 'performTraversals', start: 0, duration: 20}));
242      uiThread.timeSlices = [
243          newThreadSlice(uiThread, SCHEDULING_STATE.RUNNING, 0, 5),
244          newThreadSlice(uiThread, SCHEDULING_STATE.UNINTR_SLEEP, 5, 10),
245          newThreadSlice(uiThread, SCHEDULING_STATE.RUNNING, 15, 5)];
246    }, tr.e.audits.AndroidAuditor);
247    assert.equal(model.alerts.length, 1);
248    var alert = model.alerts[0];
249    assert.equal(alert.info.title, 'Scheduling delay');
250    assert.deepEqual(alert.args['Blocking I/O delay'], new TimeDuration(10));
251  });
252
253  test('addFramesToModel', function() {
254    var process;
255    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
256      process = model.getOrCreateProcess(1);
257      var uiThread = process.getOrCreateThread(1);
258
259      // High level choreographer frame signal
260      uiThread.sliceGroup.pushSlice(newSliceEx(
261          {title: 'Choreographer#doFrame', start: 0, duration: 8}));
262      uiThread.sliceGroup.pushSlice(newSliceEx(
263          {title: 'Choreographer#doFrame', start: 16, duration: 20}));
264
265      // Old devices only have 'performTraversals'
266      uiThread.sliceGroup.pushSlice(newSliceEx(
267          {title: 'performTraversals', start: 40, duration: 90}));
268    }, tr.e.audits.AndroidAuditor);
269
270    assert.equal(process.frames.length, 3);
271    assert.closeTo(process.frames[0].totalDuration, 8, 1e-5);
272    assert.closeTo(process.frames[1].totalDuration, 20, 1e-5);
273    assert.closeTo(process.frames[2].totalDuration, 90, 1e-5);
274
275    assert.equal(process.frames[0].perfClass,
276        FRAME_PERF_CLASS.GOOD);
277    assert.equal(process.frames[1].perfClass,
278        FRAME_PERF_CLASS.BAD);
279    assert.equal(process.frames[2].perfClass,
280        FRAME_PERF_CLASS.TERRIBLE);
281  });
282
283  test('processRenameAndSort', function() {
284    var appProcess;
285    var sfProcess;
286    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
287      appProcess = model.getOrCreateProcess(1);
288      var uiThread = appProcess.getOrCreateThread(1);
289      uiThread.name = 'ndroid.systemui';
290      uiThread.sliceGroup.pushSlice(newSliceEx(
291          {title: 'performTraversals', start: 0, duration: 8}));
292
293      sfProcess = model.getOrCreateProcess(2);
294      var sfThread = sfProcess.getOrCreateThread(2);
295      sfThread.name = '/system/bin/surfaceflinger';
296      sfThread.sliceGroup.pushSlice(newSliceEx(
297          {title: 'doComposition', start: 8, duration: 2}));
298
299    }, tr.e.audits.AndroidAuditor);
300
301    // both processes should be renamed
302    assert.equal(appProcess.name, 'android.systemui');
303    assert.equal(sfProcess.name, 'SurfaceFlinger');
304
305    assert.isTrue(sfProcess.sortIndex < appProcess.sortIndex);
306    assert.isTrue(appProcess.important);
307    assert.isFalse(sfProcess.important);
308  });
309
310  test('eventInfo', function() {
311    var eventsExpectingInfo = [];
312    var eventsNotExpectingInfo = [];
313
314    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
315      var appProcess = model.getOrCreateProcess(1);
316      var uiThread = appProcess.getOrCreateThread(1);
317      uiThread.name = 'ndroid.systemui';
318
319      var pushInfoSlice = function(slice) {
320        eventsExpectingInfo.push(slice);
321        uiThread.sliceGroup.pushSlice(slice);
322      }
323      var pushNonInfoSlice = function(slice) {
324        eventsNotExpectingInfo.push(slice);
325        uiThread.sliceGroup.pushSlice(slice);
326      }
327
328      pushInfoSlice(newSliceEx(
329          {title: 'performTraversals', start: 0, duration: 10}));
330      pushInfoSlice(newSliceEx({title: 'measure', start: 0, duration: 2}));
331      pushInfoSlice(newSliceEx({title: 'layout', start: 2, duration: 1}));
332      pushInfoSlice(newSliceEx({title: 'draw', start: 3, duration: 7}));
333
334      // Out of place slices should not be tagged.
335      pushNonInfoSlice(newSliceEx({title: 'measure', start: 11, duration: 1}));
336      pushNonInfoSlice(newSliceEx({title: 'draw', start: 12, duration: 1}));
337    }, tr.e.audits.AndroidAuditor);
338
339    eventsExpectingInfo.forEach(function(event) {
340      assert.notEqual(event.info, undefined);
341    });
342
343    eventsNotExpectingInfo.forEach(function(event) {
344      assert.equal(event.info, undefined);
345    });
346  });
347
348  test('drawingThreadPriorities', function() {
349    var uiThread;
350    var renderThread;
351    var workerThread;
352    var otherThread;
353    var model = tr.c.test_utils.newModelWithAuditor(function(model) {
354      var appProcess = model.getOrCreateProcess(1);
355
356      uiThread = appProcess.getOrCreateThread(1);
357      uiThread.name = 'ndroid.systemui';
358      uiThread.sliceGroup.pushSlice(newSliceEx(
359          {title: 'performTraversals', start: 0, duration: 4}));
360
361      renderThread = appProcess.getOrCreateThread(2);
362      renderThread.name = 'RenderThread';
363      renderThread.sliceGroup.pushSlice(newSliceEx(
364          {title: 'DrawFrame', start: 3, duration: 4}));
365
366      workerThread = appProcess.getOrCreateThread(3);
367      workerThread.name = 'hwuiTask1';
368      workerThread.sliceGroup.pushSlice(newSliceEx(
369          {title: 'work', start: 4, duration: 1}));
370
371      otherThread = appProcess.getOrCreateThread(4);
372      otherThread.name = 'other';
373      otherThread.sliceGroup.pushSlice(newSliceEx(
374          {title: 'otherWork', start: 0, duration: 2}));
375    }, tr.e.audits.AndroidAuditor);
376
377    assert.isTrue(uiThread.sortIndex < renderThread.sortIndex);
378    assert.isTrue(renderThread.sortIndex < workerThread.sortIndex);
379    assert.isTrue(workerThread.sortIndex < otherThread.sortIndex);
380  });
381
382  test('favicon', function() {
383    var createModelWithJank = function(percentageJank) {
384
385      return tr.c.test_utils.newModelWithAuditor(function(model) {
386        var uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
387        for (var i = 0; i < 100; i++) {
388          var slice = newSliceEx({
389              title: 'performTraversals',
390              start: 30 * i,
391              duration: i <= percentageJank ? 24 : 8
392          });
393          uiThread.sliceGroup.pushSlice(slice);
394        }
395      }, tr.e.audits.AndroidAuditor);
396    };
397    assert.equal(createModelWithJank(3).faviconHue, 'green');
398    assert.equal(createModelWithJank(10).faviconHue, 'yellow');
399    assert.equal(createModelWithJank(50).faviconHue, 'red');
400  });
401});
402</script>
403