| <!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="/core/test_utils.html"> |
| <link rel="import" href="/extras/android/android_auditor.html"> |
| <link rel="import" href="/extras/importer/linux_perf/ftrace_importer.html"> |
| |
| <script> |
| 'use strict'; |
| |
| tr.b.unittest.testSuite(function() { |
| var AndroidModelHelper = tr.e.audits.AndroidModelHelper; |
| var newAsyncSliceNamed = tr.c.test_utils.newAsyncSliceNamed; |
| var newSliceNamed = tr.c.test_utils.newSliceNamed; |
| var newSliceEx = tr.c.test_utils.newSliceEx; |
| var newCounterNamed = tr.c.test_utils.newCounterNamed; |
| var newCounterSeries = tr.c.test_utils.newCounterSeries; |
| |
| function createSurfaceFlingerWithVsyncs(model) { |
| if (model.getProcess(2)) |
| throw new Error('process already exists'); |
| |
| var sfProcess = model.getOrCreateProcess(2); |
| var sfThread = sfProcess.getOrCreateThread(2); // main thread, tid = pid |
| sfThread.name = '/system/bin/surfaceflinger'; |
| |
| // ensure slicegroup has data |
| sfThread.sliceGroup.pushSlice(newSliceEx({ |
| title: 'doComposition', |
| start: 8, |
| duration: 2 |
| })); |
| |
| var counter = sfProcess.getOrCreateCounter('android', 'VSYNC'); |
| var series = newCounterSeries(); |
| for (var i = 0; i <= 10; i++) { |
| series.addCounterSample(i * 10, i % 2); |
| } |
| counter.addSeries(series); |
| } |
| |
| /* |
| * List of customizeModelCallbacks which produce different 80ms frames, |
| * each starting at 10ms, and with a single important slice |
| */ |
| var SINGLE_FRAME_CUSTOM_MODELS = [ |
| function(model) { |
| // UI thread only |
| var uiThread = model.getOrCreateProcess(120).getOrCreateThread(120); |
| uiThread.sliceGroup.pushSlice(newSliceNamed('performTraversals', 10, 80)); |
| |
| model.uiThread = uiThread; |
| }, |
| |
| function(model) { |
| // RenderThread only |
| var renderThread = model.getOrCreateProcess(120).getOrCreateThread(200); |
| renderThread.name = 'RenderThread'; |
| renderThread.sliceGroup.pushSlice(newSliceNamed('doFrame', 10, 80)); |
| |
| model.renderThread = renderThread; |
| }, |
| |
| function(model) { |
| var uiThread = model.getOrCreateProcess(120).getOrCreateThread(120); |
| |
| // UI thread time - 19 (from 10 to 29) |
| uiThread.asyncSliceGroup.push( |
| newAsyncSliceNamed('deliverInputEvent', 10, 9, uiThread, uiThread)); |
| uiThread.sliceGroup.pushSlice(newSliceNamed('performTraversals', 20, 10)); |
| uiThread.sliceGroup.pushSlice(newSliceNamed('draw', 20, 8)); |
| uiThread.sliceGroup.pushSlice(newSliceNamed('Record View#draw()', 20, 8)); |
| |
| // RenderThread time - 61 (from 29 to 90) |
| var renderThread = model.getOrCreateProcess(120).getOrCreateThread(200); |
| renderThread.name = 'RenderThread'; |
| renderThread.sliceGroup.pushSlice(newSliceNamed('DrawFrame', 29, 61)); |
| renderThread.sliceGroup.pushSlice(newSliceNamed('syncFrameState', 29, 1)); |
| |
| model.uiThread = uiThread; |
| model.renderThread = renderThread; |
| } |
| ]; |
| |
| test('getThreads', function() { |
| SINGLE_FRAME_CUSTOM_MODELS.forEach(function(customizeModelCallback) { |
| var model = tr.c.test_utils.newModel(customizeModelCallback); |
| var helper = new AndroidModelHelper(model); |
| assert.equal(helper.apps[0].uiThread, model.uiThread); |
| assert.equal(helper.apps[0].renderThread, model.renderThread); |
| }); |
| }); |
| |
| test('iterateImportantSlices', function() { |
| SINGLE_FRAME_CUSTOM_MODELS.forEach(function(customizeModelCallback) { |
| var model = tr.c.test_utils.newModel(customizeModelCallback); |
| var helper = new AndroidModelHelper(model); |
| |
| var seen = 0; |
| helper.iterateImportantSlices(function(importantSlice) { |
| assert.isTrue(importantSlice instanceof tr.model.Slice); |
| seen++; |
| }); |
| assert.equal(seen, 1); |
| }); |
| }); |
| |
| test('getFrames', function() { |
| SINGLE_FRAME_CUSTOM_MODELS.forEach(function(customizeModelCallback) { |
| var model = tr.c.test_utils.newModel(customizeModelCallback); |
| var helper = new AndroidModelHelper(model); |
| assert.equal(helper.apps.length, 1); |
| |
| var frames = helper.apps[0].getFrames(); |
| assert.equal(frames.length, 1); |
| assert.closeTo(frames[0].totalDuration, 80, 1e-5); |
| |
| assert.closeTo(frames[0].start, 10, 1e-5); |
| assert.closeTo(frames[0].end, 90, 1e-5); |
| }); |
| }); |
| |
| test('surfaceFlingerVsyncs', function() { |
| var model = tr.c.test_utils.newModel(createSurfaceFlingerWithVsyncs); |
| var helper = new AndroidModelHelper(model); |
| assert.isTrue(helper.surfaceFlinger.hasVsyncs); |
| |
| // test querying the vsyncs |
| assert.closeTo(helper.surfaceFlinger.getFrameKickoff(5), 0, 1e-5); |
| assert.closeTo(helper.surfaceFlinger.getFrameDeadline(95), 100, 1e-5); |
| |
| assert.closeTo(helper.surfaceFlinger.getFrameKickoff(10), 10, 1e-5); |
| assert.closeTo(helper.surfaceFlinger.getFrameDeadline(90), 100, 1e-5); |
| |
| // test undefined behavior outside of vsyncs. |
| assert.isUndefined(helper.surfaceFlinger.getFrameKickoff(-5)); |
| assert.isUndefined(helper.surfaceFlinger.getFrameDeadline(105)); |
| }); |
| |
| test('frameVsyncInterop', function() { |
| var model = tr.c.test_utils.newModel(function(model) { |
| // app - 3 good, 3 bad frames |
| var uiThread = model.getOrCreateProcess(1).getOrCreateThread(1); |
| uiThread.sliceGroup.pushSlice(newSliceNamed('performTraversals', 1, 8)); |
| uiThread.sliceGroup.pushSlice(newSliceNamed('performTraversals', 10, 8)); |
| uiThread.sliceGroup.pushSlice(newSliceNamed('performTraversals', 20, 8)); |
| uiThread.sliceGroup.pushSlice(newSliceNamed('performTraversals', 31, 11)); |
| uiThread.sliceGroup.pushSlice(newSliceNamed('performTraversals', 45, 6)); |
| uiThread.sliceGroup.pushSlice(newSliceNamed('performTraversals', 60, 20)); |
| |
| // surface flinger - vsync every 10ms |
| createSurfaceFlingerWithVsyncs(model); |
| }); |
| var helper = new AndroidModelHelper(model); |
| |
| var frames = helper.apps[0].getFrames(); |
| assert.equal(frames.length, 6); |
| for (var i = 0; i < 6; i++) { |
| var shouldMissDeadline = i >= 3; |
| var missedDeadline = frames[i].args['deadline'] < frames[i].end; |
| assert.equal(shouldMissDeadline, missedDeadline); |
| } |
| }); |
| |
| test('appInputs', function() { |
| var model = tr.c.test_utils.newModel(function(model) { |
| var process = model.getOrCreateProcess(120); |
| var uiThread = process.getOrCreateThread(120); |
| uiThread.sliceGroup.pushSlice(newSliceNamed('performTraversals', 20, 4)); |
| uiThread.sliceGroup.pushSlice(newSliceNamed('performTraversals', 40, 4)); |
| |
| var counter = process.getOrCreateCounter('android', 'aq:pending:foo'); |
| var series = newCounterSeries(); |
| series.addCounterSample(10, 1); |
| series.addCounterSample(20, 0); |
| series.addCounterSample(30, 1); |
| series.addCounterSample(40, 2); |
| series.addCounterSample(50, 0); |
| counter.addSeries(series); |
| }); |
| var helper = new AndroidModelHelper(model); |
| assert.equal(helper.apps.length, 1); |
| |
| var inputSamples = helper.apps[0].getInputSamples(); |
| assert.equal(inputSamples.length, 3); |
| assert.equal(inputSamples[0].timestamp, 10); |
| assert.equal(inputSamples[1].timestamp, 30); |
| assert.equal(inputSamples[2].timestamp, 40); |
| }); |
| |
| test('appAnimations', function() { |
| var model = tr.c.test_utils.newModel(function(model) { |
| var process = model.getOrCreateProcess(120); |
| var uiThread = process.getOrCreateThread(120); |
| uiThread.sliceGroup.pushSlice(newSliceNamed('performTraversals', 10, 10)); |
| uiThread.asyncSliceGroup.push(newAsyncSliceNamed('animator:foo', 0, 10, |
| uiThread, uiThread)); |
| }); |
| var helper = new AndroidModelHelper(model); |
| assert.equal(helper.apps.length, 1); |
| |
| var animations = helper.apps[0].getAnimationAsyncSlices(); |
| assert.equal(animations.length, 1); |
| assert.equal(animations[0].start, 0); |
| assert.equal(animations[0].end, 10); |
| }); |
| }); |
| </script> |