1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.cts; 17 18 import android.service.GraphicsStatsHistogramBucketProto; 19 import android.service.GraphicsStatsJankSummaryProto; 20 import android.service.GraphicsStatsProto; 21 import android.service.GraphicsStatsServiceDumpProto; 22 23 import java.util.ArrayList; 24 import java.util.Date; 25 import java.util.List; 26 27 public class GraphicsStatsValidationTest extends ProtoDumpTestCase { 28 private static final String TAG = "GraphicsStatsValidationTest"; 29 30 private static final String DEVICE_SIDE_TEST_APK = "CtsGraphicsStatsApp.apk"; 31 private static final String DEVICE_SIDE_TEST_PACKAGE 32 = "com.android.server.cts.device.graphicsstats"; 33 34 @Override 35 protected void tearDown() throws Exception { 36 getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE); 37 super.tearDown(); 38 } 39 40 @Override 41 protected void setUp() throws Exception { 42 super.setUp(); 43 installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true); 44 turnScreenOn(); 45 // Ensure that we have a starting point for our stats 46 runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".SimpleDrawFrameTests", 47 "testDrawTenFrames"); 48 // Kill to ensure that stats persist/merge across process death 49 killTestApp(); 50 } 51 52 private void turnScreenOn() throws Exception { 53 getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP"); 54 getDevice().executeShellCommand("wm dismiss-keyguard"); 55 } 56 57 public void testBasicDrawFrame() throws Exception { 58 GraphicsStatsProto[] results = runDrawTest("testDrawTenFrames"); 59 GraphicsStatsProto statsBefore = results[0]; 60 GraphicsStatsProto statsAfter = results[1]; 61 GraphicsStatsJankSummaryProto summaryBefore = statsBefore.getSummary(); 62 GraphicsStatsJankSummaryProto summaryAfter = statsAfter.getSummary(); 63 assertTrue(summaryAfter.getTotalFrames() > summaryBefore.getTotalFrames()); 64 65 int frameDelta = summaryAfter.getTotalFrames() - summaryBefore.getTotalFrames(); 66 int jankyDelta = summaryAfter.getJankyFrames() - summaryBefore.getJankyFrames(); 67 // We expect 11 frames to have been drawn (first frame + the 10 more explicitly requested) 68 assertEquals(11, frameDelta); 69 assertTrue(jankyDelta < 5); 70 int veryJankyDelta = countFramesAbove(statsAfter, 40) - countFramesAbove(statsBefore, 40); 71 // The 1st frame could be >40ms, but nothing after that should be 72 assertTrue(veryJankyDelta <= 1); 73 } 74 75 public void testJankyDrawFrame() throws Exception { 76 GraphicsStatsProto[] results = runDrawTest("testDrawJankyFrames"); 77 GraphicsStatsProto statsBefore = results[0]; 78 GraphicsStatsProto statsAfter = results[1]; 79 GraphicsStatsJankSummaryProto summaryBefore = statsBefore.getSummary(); 80 GraphicsStatsJankSummaryProto summaryAfter = statsAfter.getSummary(); 81 assertTrue(summaryAfter.getTotalFrames() > summaryBefore.getTotalFrames()); 82 83 int frameDelta = summaryAfter.getTotalFrames() - summaryBefore.getTotalFrames(); 84 int jankyDelta = summaryAfter.getJankyFrames() - summaryBefore.getJankyFrames(); 85 // Test draws 50 frames + 1 initial frame. We expect 40 of them to be janky, 86 // 10 of each of ANIMATION, LAYOUT, RECORD_DRAW, and MISSED_VSYNC 87 assertEquals(51, frameDelta); 88 assertTrue(jankyDelta >= 40); 89 assertTrue(jankyDelta < 45); 90 91 // Although our current stats don't distinguish between ANIMATION, LAYOUT, and RECORD_DRAW 92 // so this will just be slowUi +30 93 int slowUiDelta = summaryAfter.getSlowUiThreadCount() - summaryBefore.getSlowUiThreadCount(); 94 assertTrue(slowUiDelta >= 30); 95 int missedVsyncDelta = summaryAfter.getMissedVsyncCount() 96 - summaryBefore.getMissedVsyncCount(); 97 assertTrue(missedVsyncDelta >= 10); 98 assertTrue(missedVsyncDelta <= 11); 99 100 int veryJankyDelta = countFramesAbove(statsAfter, 60) - countFramesAbove(statsBefore, 60); 101 // The 1st frame could be >40ms, but nothing after that should be 102 assertTrue(veryJankyDelta <= 1); 103 } 104 105 public void testDaveyDrawFrame() throws Exception { 106 GraphicsStatsProto[] results = runDrawTest("testDrawDaveyFrames"); 107 GraphicsStatsProto statsBefore = results[0]; 108 GraphicsStatsProto statsAfter = results[1]; 109 GraphicsStatsJankSummaryProto summaryBefore = statsBefore.getSummary(); 110 GraphicsStatsJankSummaryProto summaryAfter = statsAfter.getSummary(); 111 assertTrue(summaryAfter.getTotalFrames() > summaryBefore.getTotalFrames()); 112 113 int frameDelta = summaryAfter.getTotalFrames() - summaryBefore.getTotalFrames(); 114 int jankyDelta = summaryAfter.getJankyFrames() - summaryBefore.getJankyFrames(); 115 // Test draws 40 frames + 1 initial frame. We expect 10 of them to be daveys, 116 // 10 of them to be daveyjrs, and 20 to jank from missed vsync (from the davey/daveyjr prior to it) 117 assertEquals(41, frameDelta); 118 assertTrue(jankyDelta >= 20); 119 assertTrue(jankyDelta < 25); 120 121 int gt150msDelta = countFramesAbove(statsAfter, 150) - countFramesAbove(statsBefore, 150); 122 assertTrue(gt150msDelta >= 20); // 10 davey jrs + 10 daveys + maybe first frame 123 assertTrue(gt150msDelta <= 21); 124 int gt700msDelta = countFramesAbove(statsAfter, 700) - countFramesAbove(statsBefore, 700); 125 assertEquals(10, gt700msDelta); // 10 daveys 126 } 127 128 private GraphicsStatsProto[] runDrawTest(String testName) throws Exception { 129 return doRunDrawTest(testName, true); 130 } 131 132 private GraphicsStatsProto[] doRunDrawTest(String testName, boolean canRetry) throws Exception { 133 GraphicsStatsProto statsBefore = fetchStats(); 134 assertNotNull(statsBefore); 135 killTestApp(); 136 turnScreenOn(); 137 runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".SimpleDrawFrameTests", testName); 138 killTestApp(); 139 GraphicsStatsProto statsAfter = fetchStats(); 140 assertNotNull(statsAfter); 141 // If we get extremely unlucky a log rotate might have happened. If so we retry, but only once 142 // It's a failure if this test takes >24 hours such that 2 rotates could happen while running 143 // this test case, or more likely if stats are not being merged/persisted properly 144 if (canRetry) { 145 if (statsBefore.getStatsStart() != statsAfter.getStatsStart()) { 146 return doRunDrawTest(testName, false); 147 } 148 } else { 149 assertEquals(statsBefore.getStatsStart(), statsAfter.getStatsStart()); 150 } 151 validate(statsBefore); 152 validate(statsAfter); 153 return new GraphicsStatsProto[] { statsBefore, statsAfter }; 154 } 155 156 private void validate(GraphicsStatsProto proto) { 157 assertNotNull(proto.getPackageName()); 158 assertFalse(proto.getPackageName().isEmpty()); 159 assertTrue(proto.getVersionCode() > 0); 160 assertTrue(proto.getStatsStart() > 0); 161 assertTrue(proto.getStatsEnd() > 0); 162 assertTrue(proto.hasSummary()); 163 GraphicsStatsJankSummaryProto summary = proto.getSummary(); 164 assertTrue(summary.getTotalFrames() > 0); 165 // Our test app won't produce that many frames, so we can assert this is a realistic 166 // number. We cap it at 1,000,000 in case the test is repeated many, many times in one day 167 assertTrue(summary.getTotalFrames() < 1000000); 168 // We can't generically assert things about the janky frames, so just assert they fall into 169 // valid ranges. 170 assertTrue(summary.getJankyFrames() <= summary.getTotalFrames()); 171 assertTrue(summary.getMissedVsyncCount() <= summary.getJankyFrames()); 172 assertTrue(summary.getHighInputLatencyCount() <= summary.getJankyFrames()); 173 assertTrue(summary.getSlowUiThreadCount() <= summary.getJankyFrames()); 174 assertTrue(summary.getSlowBitmapUploadCount() <= summary.getJankyFrames()); 175 assertTrue(summary.getSlowDrawCount() <= summary.getJankyFrames()); 176 assertTrue(proto.getHistogramCount() > 0); 177 178 int histogramTotal = countTotalFrames(proto); 179 assertEquals(summary.getTotalFrames(), histogramTotal); 180 } 181 182 private int countFramesAbove(GraphicsStatsProto proto, int thresholdMs) { 183 int totalFrames = 0; 184 for (GraphicsStatsHistogramBucketProto bucket : proto.getHistogramList()) { 185 if (bucket.getRenderMillis() >= thresholdMs) { 186 totalFrames += bucket.getFrameCount(); 187 } 188 } 189 return totalFrames; 190 } 191 192 private int countTotalFrames(GraphicsStatsProto proto) { 193 return countFramesAbove(proto, 0); 194 } 195 196 private void killTestApp() throws Exception { 197 getDevice().executeShellCommand("am kill " + DEVICE_SIDE_TEST_PACKAGE); 198 } 199 200 private GraphicsStatsProto fetchStats() throws Exception { 201 GraphicsStatsServiceDumpProto serviceDumpProto = getDump(GraphicsStatsServiceDumpProto.parser(), 202 "dumpsys graphicsstats --proto"); 203 List<GraphicsStatsProto> protos = filterPackage(serviceDumpProto, DEVICE_SIDE_TEST_PACKAGE); 204 return findLatest(protos); 205 } 206 207 private List<GraphicsStatsProto> filterPackage(GraphicsStatsServiceDumpProto dump, String pkgName) { 208 return filterPackage(dump.getStatsList(), pkgName); 209 } 210 211 private List<GraphicsStatsProto> filterPackage(List<GraphicsStatsProto> list, String pkgName) { 212 ArrayList<GraphicsStatsProto> filtered = new ArrayList<>(); 213 for (GraphicsStatsProto proto : list) { 214 if (pkgName.equals(proto.getPackageName())) { 215 filtered.add(proto); 216 } 217 } 218 return filtered; 219 } 220 221 private GraphicsStatsProto findLatest(List<GraphicsStatsProto> list) { 222 if (list.size() == 0) { return null; } 223 GraphicsStatsProto latest = list.get(0); 224 Date latestDate = new Date(); 225 Date compareTo = new Date(); 226 latestDate.setTime(latest.getStatsEnd()); 227 for (int i = 1; i < list.size(); i++) { 228 GraphicsStatsProto proto = list.get(i); 229 compareTo.setTime(proto.getStatsEnd()); 230 if (compareTo.after(latestDate)) { 231 latestDate.setTime(proto.getStatsEnd()); 232 latest = proto; 233 } 234 } 235 return latest; 236 } 237 } 238