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 17 package android.dumpsys.cts; 18 19 import com.android.tradefed.log.LogUtil.CLog; 20 21 import java.io.BufferedReader; 22 import java.io.StringReader; 23 import java.util.HashSet; 24 import java.util.Set; 25 import java.util.regex.Matcher; 26 import java.util.regex.Pattern; 27 28 /** 29 * Test to check the format of the dumps of the processstats test. 30 */ 31 public class ProcessStatsDumpsysTest extends BaseDumpsysTest { 32 private static final String DEVICE_SIDE_TEST_APK = "CtsProcStatsApp.apk"; 33 private static final String DEVICE_SIDE_TEST_PACKAGE = "com.android.server.cts.procstats"; 34 35 private static final String DEVICE_SIDE_HELPER_APK = "CtsProcStatsHelperApp.apk"; 36 private static final String DEVICE_SIDE_HELPER_PACKAGE = "com.android.server.cts.procstatshelper"; 37 38 /** 39 * Maximum allowance scale factor when checking a duration time. 40 * 41 * If [actual value] > [expected value] * {@link #DURATION_TIME_MAX_FACTOR}, 42 * then the test fails. 43 * 44 * Because the run duration time may include the process startup time, we need a rather big 45 * allowance. 46 */ 47 private static final double DURATION_TIME_MAX_FACTOR = 2; 48 49 /** 50 * Tests the output of "dumpsys procstats -c". This is a proxy for testing "dumpsys procstats 51 * --checkin", since the latter is not idempotent. 52 */ 53 public void testProcstatsOutput() throws Exception { 54 if (true) { 55 return; // TODO http://b/70858729 56 } 57 // First, run the helper app so that we have some interesting records in the output. 58 checkWithProcStatsApp(); 59 60 String procstats = mDevice.executeShellCommand("dumpsys procstats -c"); 61 assertNotNull(procstats); 62 assertTrue(procstats.length() > 0); 63 64 final int sep24h = procstats.indexOf("AGGREGATED OVER LAST 24 HOURS:"); 65 final int sep3h = procstats.indexOf("AGGREGATED OVER LAST 3 HOURS:"); 66 67 assertTrue("24 hour stats not found.", sep24h > 1); 68 assertTrue("3 hour stats not found.", sep3h > 1); 69 70 // Current 71 checkProcStateOutput(procstats.substring(0, sep24h), /*checkAvg=*/ true); 72 73 // Last 24 hours 74 checkProcStateOutput(procstats.substring(sep24h, sep3h), /*checkAvg=*/ false); 75 76 // Last 3 hours 77 checkProcStateOutput(procstats.substring(sep3h), /*checkAvg=*/ false); 78 } 79 80 private static String[] commaSplit(String line) { 81 if (line.endsWith(",")) { 82 line = line + " "; 83 } 84 final String[] values = line.split(","); 85 if (" ".equals(values[values.length - 1])) { 86 values[values.length - 1] = ""; 87 } 88 return values; 89 } 90 91 private void checkProcStateOutput(String text, boolean checkAvg) throws Exception { 92 final Set<String> seenTags = new HashSet<>(); 93 94 try (BufferedReader reader = new BufferedReader( 95 new StringReader(text))) { 96 97 String line; 98 while ((line = reader.readLine()) != null) { 99 if (line.isEmpty()) { 100 continue; 101 } 102 CLog.d("Checking line: " + line); 103 104 String[] parts = commaSplit(line); 105 seenTags.add(parts[0]); 106 107 switch (parts[0]) { 108 case "vers": 109 assertEquals(2, parts.length); 110 assertEquals(5, Integer.parseInt(parts[1])); 111 break; 112 case "period": 113 checkPeriod(parts); 114 break; 115 case "pkgproc": 116 checkPkgProc(parts); 117 break; 118 case "pkgpss": 119 checkPkgPss(parts, checkAvg); 120 break; 121 case "pkgsvc-bound": 122 case "pkgsvc-exec": 123 case "pkgsvc-run": 124 case "pkgsvc-start": 125 checkPkgSvc(parts); 126 break; 127 case "pkgkills": 128 checkPkgKills(parts, checkAvg); 129 break; 130 case "proc": 131 checkProc(parts); 132 break; 133 case "pss": 134 checkPss(parts, checkAvg); 135 break; 136 case "kills": 137 checkKills(parts, checkAvg); 138 break; 139 case "total": 140 checkTotal(parts); 141 break; 142 default: 143 break; 144 } 145 } 146 } 147 148 assertSeenTag(seenTags, "vers"); 149 assertSeenTag(seenTags, "period"); 150 assertSeenTag(seenTags, "pkgproc"); 151 assertSeenTag(seenTags, "proc"); 152 assertSeenTag(seenTags, "pss"); 153 assertSeenTag(seenTags, "total"); 154 assertSeenTag(seenTags, "weights"); 155 assertSeenTag(seenTags, "availablepages"); 156 } 157 158 private void checkPeriod(String[] parts) { 159 assertTrue("Length should be >= 5, found: " + parts.length, 160 parts.length >= 5); 161 assertNotNull(parts[1]); // date 162 assertLesserOrEqual(parts[2], parts[3]); // start time and end time (msec) 163 for (int i = 4; i < parts.length; i++) { 164 switch (parts[i]) { 165 case "shutdown": 166 case "sysprops": 167 case "complete": 168 case "partial": 169 case "swapped-out-pss": 170 continue; 171 } 172 fail("Invalid value '" + parts[i] + "' found."); 173 } 174 } 175 176 private void checkPkgProc(String[] parts) { 177 int statesStartIndex; 178 179 assertTrue(parts.length >= 5); 180 assertNotNull(parts[1]); // package name 181 assertNonNegativeInteger(parts[2]); // uid 182 assertNonNegativeInteger(parts[3]); // app version 183 assertNotNull(parts[4]); // process 184 statesStartIndex = 5; 185 186 for (int i = statesStartIndex; i < parts.length; i++) { 187 String[] subparts = parts[i].split(":"); 188 assertEquals(2, subparts.length); 189 checkTag(subparts[0], true); // tag 190 assertNonNegativeInteger(subparts[1]); // duration (msec) 191 } 192 } 193 194 private void checkTag(String tag, boolean hasProcess) { 195 assertEquals(hasProcess ? 3 : 2, tag.length()); 196 197 // screen: 0 = off, 1 = on 198 char s = tag.charAt(0); 199 if (s != '0' && s != '1') { 200 fail("malformed tag: " + tag); 201 } 202 203 // memory: n = normal, m = moderate, l = low, c = critical 204 char m = tag.charAt(1); 205 if (m != 'n' && m != 'm' && m != 'l' && m != 'c') { 206 fail("malformed tag: " + tag); 207 } 208 209 if (hasProcess) { 210 char p = tag.charAt(2); 211 assertTrue("malformed tag: " + tag, "ptfbuwsxrhlace".indexOf(p) >= 0); 212 } 213 } 214 215 private void checkPkgPss(String[] parts, boolean checkAvg) { 216 int statesStartIndex; 217 218 assertTrue(parts.length >= 5); 219 assertNotNull(parts[1]); // package name 220 assertNonNegativeInteger(parts[2]); // uid 221 assertNonNegativeInteger(parts[3]); // app version 222 assertNotNull(parts[4]); // process 223 statesStartIndex = 5; 224 225 for (int i = statesStartIndex; i < parts.length; i++) { 226 String[] subparts = parts[i].split(":"); 227 assertEquals(8, subparts.length); 228 checkTag(subparts[0], true); // tag 229 assertNonNegativeInteger(subparts[1]); // sample size 230 assertMinAvgMax(subparts[2], subparts[3], subparts[4], checkAvg); // pss 231 assertMinAvgMax(subparts[5], subparts[6], subparts[7], checkAvg); // uss 232 } 233 } 234 235 private void checkPkgSvc(String[] parts) { 236 int statesStartIndex; 237 238 assertTrue(parts.length >= 6); 239 assertNotNull(parts[1]); // package name 240 assertNonNegativeInteger(parts[2]); // uid 241 assertNonNegativeInteger(parts[3]); // app version 242 assertNotNull(parts[4]); // service name 243 assertNonNegativeInteger(parts[5]); // count 244 statesStartIndex = 6; 245 246 for (int i = statesStartIndex; i < parts.length; i++) { 247 String[] subparts = parts[i].split(":"); 248 assertEquals(2, subparts.length); 249 checkTag(subparts[0], false); // tag 250 assertNonNegativeInteger(subparts[1]); // duration (msec) 251 } 252 } 253 254 private void checkPkgKills(String[] parts, boolean checkAvg) { 255 String pssStr; 256 257 assertEquals(9, parts.length); 258 assertNotNull(parts[1]); // package name 259 assertNonNegativeInteger(parts[2]); // uid 260 assertNonNegativeInteger(parts[3]); // app version 261 assertNotNull(parts[4]); // process 262 assertNonNegativeInteger(parts[5]); // wakes 263 assertNonNegativeInteger(parts[6]); // cpu 264 assertNonNegativeInteger(parts[7]); // cached 265 pssStr = parts[8]; 266 267 String[] subparts = pssStr.split(":"); 268 assertEquals(3, subparts.length); 269 assertMinAvgMax(subparts[0], subparts[1], subparts[2], checkAvg); // pss 270 } 271 272 private void checkProc(String[] parts) { 273 assertTrue(parts.length >= 3); 274 assertNotNull(parts[1]); // package name 275 assertNonNegativeInteger(parts[2]); // uid 276 277 for (int i = 3; i < parts.length; i++) { 278 String[] subparts = parts[i].split(":"); 279 assertEquals(2, subparts.length); 280 checkTag(subparts[0], true); // tag 281 assertNonNegativeInteger(subparts[1]); // duration (msec) 282 } 283 } 284 285 private void checkPss(String[] parts, boolean checkAvg) { 286 assertTrue(parts.length >= 3); 287 assertNotNull(parts[1]); // package name 288 assertNonNegativeInteger(parts[2]); // uid 289 290 for (int i = 3; i < parts.length; i++) { 291 String[] subparts = parts[i].split(":"); 292 assertEquals(8, subparts.length); 293 checkTag(subparts[0], true); // tag 294 assertNonNegativeInteger(subparts[1]); // sample size 295 assertMinAvgMax(subparts[2], subparts[3], subparts[4], checkAvg); // pss 296 assertMinAvgMax(subparts[5], subparts[6], subparts[7], checkAvg); // uss 297 } 298 } 299 300 private void checkKills(String[] parts, boolean checkAvg) { 301 assertEquals(7, parts.length); 302 assertNotNull(parts[1]); // package name 303 assertNonNegativeInteger(parts[2]); // uid 304 assertNonNegativeInteger(parts[3]); // wakes 305 assertNonNegativeInteger(parts[4]); // cpu 306 assertNonNegativeInteger(parts[5]); // cached 307 String pssStr = parts[6]; 308 309 String[] subparts = pssStr.split(":"); 310 assertEquals(3, subparts.length); 311 assertMinAvgMax(subparts[0], subparts[1], subparts[2], checkAvg); // pss 312 } 313 314 private void checkTotal(String[] parts) { 315 assertTrue(parts.length >= 2); 316 for (int i = 1; i < parts.length; i++) { 317 String[] subparts = parts[i].split(":"); 318 checkTag(subparts[0], false); // tag 319 320 assertNonNegativeInteger(subparts[1]); // duration (msec) 321 } 322 } 323 324 /** 325 * Find the first line with the prefix, and return the rest of the line. 326 */ 327 private static String findLine(String prefix, String[] lines) { 328 for (String line : lines) { 329 if (line.startsWith(prefix)) { 330 CLog.d("Found line: " + line); 331 return line.substring(prefix.length()); 332 } 333 } 334 fail("Line with prefix '" + prefix + "' not found."); 335 return null; 336 } 337 338 private static long getTagValueSum(String[] parts, String tagRegex) { 339 final Pattern tagPattern = Pattern.compile("^" + tagRegex + "\\:"); 340 341 boolean found = false; 342 long sum = 0; 343 for (int i = 0; i < parts.length; i++){ 344 final String part = parts[i]; 345 final Matcher m = tagPattern.matcher(part); 346 if (!m.find()) { 347 continue; 348 } 349 // Extract the rest of the part and parse as a long. 350 sum += assertInteger(parts[i].substring(m.end(0))); 351 found = true; 352 } 353 assertTrue("Tag '" + tagRegex + "' not found.", found); 354 return sum; 355 } 356 357 private static void assertTagValueLessThan(String[] parts, String tagRegex, 358 long expectedMax) { 359 final long sum = getTagValueSum(parts, tagRegex); 360 361 assertTrue("Total values for '" + tagRegex 362 + "' expected to be <= (" + expectedMax + ") but was: " 363 + sum, sum <= expectedMax); 364 } 365 366 private static void assertTagValueSumAbout(String[] parts, String tagRegex, 367 long expectedValue) { 368 final long sum = getTagValueSum(parts, tagRegex); 369 370 assertTrue("Total values for '" + tagRegex 371 + "' expected to be >= " + expectedValue + " but was: " 372 + sum, sum >= expectedValue); 373 assertTrue("Total values for '" + tagRegex 374 + "' expected to be <= (" + expectedValue + ") * " 375 + DURATION_TIME_MAX_FACTOR + " but was: " 376 + sum, sum <= (expectedValue * DURATION_TIME_MAX_FACTOR)); 377 } 378 379 private void checkWithProcStatsApp() throws Exception { 380 getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE); 381 getDevice().uninstallPackage(DEVICE_SIDE_HELPER_PACKAGE); 382 383 final long startNs = System.nanoTime(); 384 385 installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true); 386 installPackage(DEVICE_SIDE_HELPER_APK, /* grantPermissions= */ true); 387 388 final int helperAppUid = Integer.parseInt(execCommandAndGetFirstGroup( 389 "dumpsys package " + DEVICE_SIDE_HELPER_PACKAGE, "userId=(\\d+)")); 390 final String uid = String.valueOf(helperAppUid); 391 392 CLog.i("Start: Helper app UID: " + helperAppUid); 393 394 try { 395 // Run the device side test which makes some network requests. 396 runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, 397 "com.android.server.cts.procstats.ProcStatsTest", "testLaunchApp"); 398 } finally { 399 getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE); 400 getDevice().uninstallPackage(DEVICE_SIDE_HELPER_PACKAGE); 401 } 402 final long finishNs = System.nanoTime(); 403 CLog.i("Finish: Took " + ((finishNs - startNs) / 1000000) + " ms"); 404 405 // The total running duration should be less than this, since we've uninstalled the app. 406 final long maxRunTime = (finishNs - startNs) / 1000000; 407 408 // Get the current procstats. 409 final String procstats = mDevice.executeShellCommand("dumpsys procstats -c --current"); 410 assertNotNull(procstats); 411 assertTrue(procstats.length() > 0); 412 413 final String[] lines = procstats.split("\n"); 414 415 // Start checking. 416 String parts[] = commaSplit(findLine( 417 "pkgproc,com.android.server.cts.procstatshelper,$U,32123,,".replace("$U", uid), 418 lines)); 419 assertTagValueSumAbout(parts, "0.t", 2000); // Screen off, foreground activity. 420 assertTagValueSumAbout(parts, "1.t", 2000); // Screen on, foreground activity. 421 assertTagValueSumAbout(parts, "1.f", 1000); // Screen on, foreground service. 422 assertTagValueSumAbout(parts, "1.s", 500); // Screen on, background service. 423 assertTagValueLessThan(parts, "...", maxRunTime); // total time. 424 425 // We can't really assert there's always "pss". If there is, then we do check the format in 426 // checkProcStateOutput(). 427 // parts = commaSplit(findLine( 428 // "pkgpss,com.android.server.cts.procstatshelper,$U,32123,,".replace("$U", uid), 429 // lines)); 430 431 parts = commaSplit(findLine( 432 ("pkgproc,com.android.server.cts.procstatshelper,$U,32123," 433 + "com.android.server.cts.procstatshelper:proc2,").replace("$U", uid), 434 lines)); 435 436 assertTagValueSumAbout(parts, "0.f", 1000); // Screen off, foreground service. 437 assertTagValueSumAbout(parts, "0.s", 500); // Screen off, background service. 438 439 assertTagValueLessThan(parts, "...", maxRunTime); // total time. 440 441 // We can't really assert there's always "pss". If there is, then we do check the format in 442 // checkProcStateOutput(). 443 // parts = commaSplit(findLine( 444 // ("pkgpss,com.android.server.cts.procstatshelper,$U,32123," 445 // + "com.android.server.cts.procstatshelper:proc2,").replace("$U", uid), 446 // lines)); 447 448 parts = commaSplit(findLine( 449 ("pkgsvc-run,com.android.server.cts.procstatshelper,$U,32123," 450 + ".ProcStatsHelperServiceMain,").replace("$U", uid), 451 lines)); 452 453 assertTagValueSumAbout(parts, "1.", 1500); // Screen on, running. 454 455 parts = commaSplit(findLine( 456 ("pkgsvc-start,com.android.server.cts.procstatshelper,$U,32123," 457 + ".ProcStatsHelperServiceMain,").replace("$U", uid), 458 lines)); 459 460 assertTagValueSumAbout(parts, "1.", 1500); // Screen on, running. 461 462 // Dose it always exist? 463 // parts = commaSplit(findLine( 464 // ("pkgsvc-exec,com.android.server.cts.procstatshelper,$U,32123," 465 // + ".ProcStatsHelperServiceMain,").replace("$U", uid), 466 // lines)); 467 468 parts = commaSplit(findLine( 469 ("pkgsvc-run,com.android.server.cts.procstatshelper,$U,32123," 470 + ".ProcStatsHelperServiceSub,").replace("$U", uid), 471 lines)); 472 473 assertTagValueSumAbout(parts, "0.", 1500); // Screen off, running. 474 475 parts = commaSplit(findLine( 476 ("pkgsvc-start,com.android.server.cts.procstatshelper,$U,32123," 477 + ".ProcStatsHelperServiceSub,").replace("$U", uid), 478 lines)); 479 480 assertTagValueSumAbout(parts, "0.", 1500); // Screen off, running. 481 482 // Dose it always exist? 483 // parts = commaSplit(findLine( 484 // ("pkgsvc-exec,com.android.server.cts.procstatshelper,$U,32123," 485 // + ".ProcStatsHelperServiceSub,").replace("$U", uid), 486 // lines)); 487 488 } 489 } 490