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