1 /* 2 * Copyright (C) 2015 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 17 package android.atrace.cts; 18 19 import com.android.cts.migration.MigrationHelper; 20 import com.android.ddmlib.Log; 21 import com.android.tradefed.build.IBuildInfo; 22 import com.android.tradefed.device.ITestDevice; 23 import com.android.tradefed.testtype.DeviceTestCase; 24 import com.android.tradefed.testtype.IBuildReceiver; 25 26 import java.io.BufferedReader; 27 import java.io.File; 28 import java.io.Reader; 29 import java.io.StringReader; 30 import java.util.Arrays; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Set; 34 import java.util.regex.Matcher; 35 import java.util.regex.Pattern; 36 37 /** 38 * Test to check that atrace is usable, to enable usage of systrace. 39 */ 40 public class AtraceHostTest extends DeviceTestCase implements IBuildReceiver { 41 private static final String TEST_APK = "CtsAtraceTestApp.apk"; 42 private static final String TEST_PKG = "com.android.cts.atracetestapp"; 43 44 private interface FtraceEntryCallback { 45 void onTraceEntry(String threadName, int pid, int tid, String eventType, String args); 46 void onFinished(); 47 } 48 49 /** 50 * Helper for parsing ftrace data. 51 * Regexs copied from (and should be kept in sync with) ftrace importer in catapult. 52 */ 53 private static class FtraceParser { 54 // Matches the trace record in 3.2 and later with the print-tgid option: 55 // <idle>-0 0 [001] d... 1.23: sched_switch 56 private static final Pattern sLineWithTgid = Pattern.compile( 57 "^\\s*(.+)-(\\d+)\\s+\\(\\s*(\\d+|-+)\\)\\s\\[(\\d+)\\]" 58 + "\\s+[dX.][N.][Hhs.][0-9a-f.]" 59 + "\\s+(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)"); 60 61 // Matches the default trace record in 3.2 and later (includes irq-info): 62 // <idle>-0 [001] d... 1.23: sched_switch 63 private static final Pattern sLineWithIrqInfo = Pattern.compile( 64 "^\\s*(.+)-(\\d+)\\s+\\[(\\d+)\\]" 65 + "\\s+[dX.][N.][Hhs.][0-9a-f.]" 66 + "\\s+(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)$"); 67 68 // Matches the default trace record pre-3.2: 69 // <idle>-0 [001] 1.23: sched_switch 70 private static final Pattern sLineLegacy = Pattern.compile( 71 "^\\s*(.+)-(\\d+)\\s+\\[(\\d+)\\]\\s*(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)"); 72 private static void parseLine(String line, FtraceEntryCallback callback) { 73 Matcher m = sLineWithTgid.matcher(line); 74 if (m.matches()) { 75 callback.onTraceEntry( 76 /*threadname*/ m.group(1), 77 /*pid*/ m.group(3).startsWith("-") ? -1 : Integer.parseInt(m.group(3)), 78 /*tid*/ Integer.parseInt(m.group(2)), 79 /*eventName*/ m.group(6), 80 /*details*/ m.group(7)); 81 return; 82 } 83 84 m = sLineWithIrqInfo.matcher(line); 85 if (m.matches()) { 86 callback.onTraceEntry( 87 /*threadname*/ m.group(1), 88 /*pid*/ -1, 89 /*tid*/ Integer.parseInt(m.group(2)), 90 /*eventName*/ m.group(5), 91 /*details*/ m.group(6)); 92 return; 93 } 94 95 m = sLineLegacy.matcher(line); 96 if (m.matches()) { 97 callback.onTraceEntry( 98 /*threadname*/ m.group(1), 99 /*pid*/ -1, 100 /*tid*/ Integer.parseInt(m.group(2)), 101 /*eventName*/ m.group(5), 102 /*details*/ m.group(6)); 103 return; 104 } 105 System.err.println("line doesn't match: " + line); 106 } 107 108 private static void parse(Reader reader, FtraceEntryCallback callback) throws Exception { 109 try { 110 BufferedReader bufferedReader = new BufferedReader(reader); 111 String line; 112 while ((line = bufferedReader.readLine()) != null) { 113 FtraceParser.parseLine(line, callback); 114 } 115 } finally { 116 callback.onFinished(); 117 } 118 } 119 } 120 121 private IBuildInfo mCtsBuild; 122 123 /** 124 * {@inheritDoc} 125 */ 126 @Override 127 public void setBuild(IBuildInfo buildInfo) { 128 mCtsBuild = buildInfo; 129 } 130 131 // Collection of all userspace tags, and 'sched' 132 private static final List<String> sRequiredCategoriesList = Arrays.asList( 133 "sched", 134 "gfx", 135 "input", 136 "view", 137 "webview", 138 "wm", 139 "am", 140 "sm", 141 "audio", 142 "video", 143 "camera", 144 "hal", 145 "app", 146 "res", 147 "dalvik", 148 "rs", 149 "bionic", 150 "power" 151 ); 152 153 /** 154 * Tests that atrace exists and is runnable with no args 155 */ 156 public void testSimpleRun() throws Exception { 157 String output = getDevice().executeShellCommand("atrace"); 158 String[] lines = output.split("\\r?\\n"); 159 160 // check for expected stdout 161 assertEquals("capturing trace... done", lines[0]); 162 assertEquals("TRACE:", lines[1]); 163 164 // commented trace marker starts here 165 assertEquals("# tracer: nop", lines[2]); 166 } 167 168 /** 169 * Tests the output of "atrace --list_categories" to ensure required categories exist. 170 */ 171 public void testCategories() throws Exception { 172 String output = getDevice().executeShellCommand("atrace --list_categories"); 173 String[] categories = output.split("\\r?\\n"); 174 175 Set<String> requiredCategories = new HashSet<String>(sRequiredCategoriesList); 176 177 for (String category : categories) { 178 int dashIndex = category.indexOf("-"); 179 180 assertTrue(dashIndex > 1); // must match category output format 181 category = category.substring(0, dashIndex).trim(); 182 183 requiredCategories.remove(category); 184 } 185 186 if (!requiredCategories.isEmpty()) { 187 for (String missingCategory : requiredCategories) { 188 System.err.println("missing category: " + missingCategory); 189 } 190 fail("Expected categories missing from atrace"); 191 } 192 } 193 194 /** 195 * Tests that atrace captures app launch, including app level tracing 196 */ 197 public void testTracingContent() throws Exception { 198 String atraceOutput = null; 199 try { 200 // cleanup test apps that might be installed from previous partial test run 201 getDevice().uninstallPackage(TEST_PKG); 202 203 // install the test app 204 File testAppFile = MigrationHelper.getTestFile(mCtsBuild, TEST_APK); 205 String installResult = getDevice().installPackage(testAppFile, false); 206 assertNull( 207 String.format("failed to install atrace test app. Reason: %s", installResult), 208 installResult); 209 210 // capture a launch of the app with async tracing 211 // content traced by 'view' tag tested below, 'sched' used to ensure tgid printed 212 String atraceArgs = "-a " + TEST_PKG + " -c -b 16000 view"; // TODO: zipping 213 getDevice().executeShellCommand("atrace --async_stop " + atraceArgs); 214 getDevice().executeShellCommand("atrace --async_start " + atraceArgs); 215 getDevice().executeShellCommand("am start " + TEST_PKG); 216 getDevice().executeShellCommand("sleep 1"); 217 atraceOutput = getDevice().executeShellCommand("atrace --async_stop " + atraceArgs); 218 } finally { 219 assertNotNull("unable to capture atrace output", atraceOutput); 220 getDevice().uninstallPackage(TEST_PKG); 221 } 222 223 224 // now parse the trace data (see external/chromium-trace/systrace.py) 225 final String MARKER = "TRACE:"; 226 int dataStart = atraceOutput.indexOf(MARKER); 227 assertTrue(dataStart >= 0); 228 String traceData = atraceOutput.substring(dataStart + MARKER.length()); 229 230 FtraceEntryCallback callback = new FtraceEntryCallback() { 231 private int userSpaceMatches = 0; 232 private int beginMatches = 0; 233 private int nextSectionIndex = -1; 234 private int appTid = -1; 235 236 237 private final String initialSection = "traceable-app-test-section"; 238 // list of tags expected to be seen on app launch, in order, after the initial. 239 private final String[] requiredSectionList = { 240 "inflate", 241 "Choreographer#doFrame", 242 "traversal", 243 "measure", 244 "layout", 245 "draw", 246 "Record View#draw()" 247 }; 248 249 @Override 250 public void onTraceEntry(String truncatedThreadName, int pid, int tid, 251 String eventName, String details) { 252 if (!"tracing_mark_write".equals(eventName)) { 253 // not userspace trace, ignore 254 return; 255 } 256 257 assertNotNull(truncatedThreadName); 258 assertTrue(tid > 0); 259 userSpaceMatches++; 260 261 if (details == null || !details.startsWith("B|")) { 262 // not a begin event 263 return; 264 } 265 beginMatches++; 266 267 if (details.endsWith("|" + initialSection)) { 268 // initial section observed, start looking for others in order 269 assertEquals(nextSectionIndex, -1); 270 nextSectionIndex = 0; 271 appTid = tid; 272 return; 273 } 274 275 if (nextSectionIndex >= 0 276 && tid == appTid 277 && nextSectionIndex < requiredSectionList.length 278 && details.endsWith("|" + requiredSectionList[nextSectionIndex])) { 279 // found next required section in sequence 280 nextSectionIndex++; 281 } 282 } 283 284 @Override 285 public void onFinished() { 286 assertTrue("Unable to parse any userspace sections from atrace output", 287 userSpaceMatches != 0); 288 assertTrue("Unable to parse any section begin events from atrace output", 289 beginMatches != 0); 290 assertTrue("Unable to parse initial userspace sections from test app", 291 nextSectionIndex >= 0); 292 assertEquals("Didn't see required list of traced sections, in order", 293 requiredSectionList.length, nextSectionIndex); 294 } 295 }; 296 297 FtraceParser.parse(new StringReader(traceData), callback); 298 } 299 } 300