1 /* 2 * Copyright (C) 2022 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.helpers.tests; 17 18 import static org.junit.Assert.assertEquals; 19 import static org.junit.Assert.assertFalse; 20 import static org.junit.Assert.assertNull; 21 import static org.junit.Assert.assertTrue; 22 23 import androidx.test.runner.AndroidJUnit4; 24 25 import com.android.helpers.BugReportDurationHelper; 26 import com.android.helpers.BugReportDurationHelper.BugReportDurationLines; 27 import com.android.helpers.BugReportDurationHelper.DumpstateBoardLines; 28 29 import org.junit.After; 30 import org.junit.Before; 31 import org.junit.Test; 32 import org.junit.runner.RunWith; 33 34 import java.io.BufferedOutputStream; 35 import java.io.File; 36 import java.io.FileOutputStream; 37 import java.io.IOException; 38 import java.nio.file.Files; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.zip.ZipEntry; 44 import java.util.zip.ZipOutputStream; 45 46 /** 47 * Android Unit tests for {@link BugReportDurationHelper}. 48 * 49 * <p>atest CollectorsHelperTest:com.android.helpers.BugReportDurationHelperTest 50 */ 51 @RunWith(AndroidJUnit4.class) 52 public class BugReportDurationHelperTest { 53 54 private static final String TAG = BugReportDurationHelperTest.class.getSimpleName(); 55 56 // Comparison of floating-point numbers with assertEquals requires a maximum delta. 57 private static final double DELTA = 0.00001; 58 59 private BugReportDurationHelper helper; 60 private File testDir; 61 62 @Before setUp()63 public void setUp() throws IOException { 64 testDir = Files.createTempDirectory("test_dir").toFile(); 65 helper = new BugReportDurationHelper(testDir.getPath()); 66 helper.startCollecting(); 67 } 68 69 @After tearDown()70 public void tearDown() { 71 // stopCollecting() is currently a no-op but is included here in case it is updated. 72 helper.stopCollecting(); 73 74 // Deletes the files in the test directory, then the test directory. 75 File[] files = testDir.listFiles(); 76 for (File f : files) { 77 if (f.isFile()) { 78 f.delete(); 79 } 80 } 81 testDir.delete(); 82 } 83 84 // Creates a .zip archive with an identically-named .txt file and a dumpstate_board file 85 // containing the input lines. createArchive( String name, List<String> bugReportLines, List<String> dumpstateBoardLines)86 private File createArchive( 87 String name, List<String> bugReportLines, List<String> dumpstateBoardLines) 88 throws IOException { 89 File f = new File(testDir, name + ".zip"); 90 FileOutputStream fos = new FileOutputStream(f); 91 BufferedOutputStream bos = new BufferedOutputStream(fos); 92 ZipOutputStream zos = new ZipOutputStream(bos); 93 try { 94 if (bugReportLines != null) { 95 zos.putNextEntry(new ZipEntry(name + ".txt")); 96 for (String line : bugReportLines) { 97 zos.write((line + "\n").getBytes()); 98 } 99 zos.closeEntry(); 100 } 101 if (dumpstateBoardLines != null) { 102 zos.putNextEntry(new ZipEntry("dumpstate_board.txt")); 103 for (String line : dumpstateBoardLines) { 104 zos.write((line + "\n").getBytes()); 105 } 106 zos.closeEntry(); 107 } 108 } finally { 109 zos.close(); 110 } 111 return f; 112 } 113 114 @Test testGetMetrics()115 public void testGetMetrics() throws IOException { 116 List<String> bugReportLines = 117 Arrays.asList( 118 "------ 44.619s was the duration of \'dumpstate_board()\' ------", 119 "------ 21.397s was the duration of \'DUMPSYS\' ------", 120 "------ 0.022s was the duration of \'DUMPSYS CRITICAL PROTO\' ------", 121 "--------- 0.051s was the duration of dumpsys SurfaceFlinger, ending at:" 122 + " 2023-04-27 23:50:35", 123 "--------- 24.741s was the duration of dumpsys meminfo, ending at:" 124 + " 2023-04-27 23:51:38", 125 "unrelated log line"); 126 List<String> dumpstateBoardLines = 127 Arrays.asList( 128 "------ Section end: dump_display ------", 129 "Elapsed msec: 103", 130 "------ Section end: dump_modemlog ------", 131 "Elapsed msec: 12532", 132 "------ Section end: dump_thermal.sh ------", 133 "Elapsed msec: 7705", 134 "unrelated line"); 135 136 createArchive("bugreport", bugReportLines, dumpstateBoardLines); 137 138 Map<String, Double> metrics = helper.getMetrics(); 139 assertEquals(9, metrics.size()); 140 assertEquals(44.619, metrics.get("bugreport-duration-dumpstate_board()"), DELTA); 141 assertEquals(21.397, metrics.get("bugreport-duration-dumpsys"), DELTA); 142 assertEquals(0.022, metrics.get("bugreport-duration-dumpsys-critical-proto"), DELTA); 143 assertEquals(0.051, metrics.get("bugreport-dumpsys-duration-SurfaceFlinger"), DELTA); 144 assertEquals(24.741, metrics.get("bugreport-dumpsys-duration-meminfo"), DELTA); 145 assertEquals(103, metrics.get("bugreport-dumpstate_board-duration-dump_display"), DELTA); 146 assertEquals(12532, metrics.get("bugreport-dumpstate_board-duration-dump_modemlog"), DELTA); 147 assertEquals( 148 7705, metrics.get("bugreport-dumpstate_board-duration-dump_thermal.sh"), DELTA); 149 assertEquals(0, metrics.get("dumpstate-section-timeout-count"), DELTA); 150 } 151 152 @Test testGetLatestBugReport()153 public void testGetLatestBugReport() throws IOException { 154 List<String> empty = new ArrayList<>(); 155 createArchive("bugreport-2022-04-23-03-12-33", empty, empty); 156 createArchive("bugreport-2022-04-20-21-44-11", empty, empty); 157 createArchive("bugreport-2021-12-28-10-32-10", empty, empty); 158 assertEquals("bugreport-2022-04-23-03-12-33.zip", helper.getLatestBugReport()); 159 } 160 161 @Test testExtractAndFilterBugReport()162 public void testExtractAndFilterBugReport() throws IOException { 163 // dumpstate section lines 164 String dumpstateLine1 = "------ 44.619s was the duration of \'dumpstate_board()\' ------"; 165 String dumpstateLine2 = "------ 21.397s was the duration of \'DUMPSYS\' ------"; 166 167 // dumpsys section lines 168 String dumpsysLine1 = 169 "--------- 0.051s was the duration of dumpsys SurfaceFlinger, ending at: 2023-04-27" 170 + " 23:50:35"; 171 String dumpsysLine2 = 172 "--------- 24.741s was the duration of dumpsys meminfo, ending at: 2023-04-27" 173 + " 23:51:38"; 174 175 // Lines that should be filtered out 176 String invalidLine = "unrelated log line"; 177 String showmapLine = 178 "------ 0.076s was the duration of \'SHOW MAP 22930 (com.android.chrome)\' ------"; 179 180 // Timeout lines should be gathered from logs instead of the "raw" text, as the latter is a 181 // subset of the former. 182 String validTimeoutLine = 183 "02-12 16:34:21.826 shell 14095 14095 E dumpstate: " 184 + "*** command '/system/xbin/su root bugreport_procdump' " 185 + "timed out after 10.002s (killing pid 15063)"; 186 String invalidTimeoutLine = 187 "*** command '/system/xbin/su root bugreport_procdump' " 188 + "timed out after 10.002s (killing pid 15063)"; 189 List<String> lines = 190 Arrays.asList( 191 dumpstateLine1, 192 dumpstateLine2, 193 dumpsysLine1, 194 dumpsysLine2, 195 invalidLine, 196 showmapLine, 197 validTimeoutLine, 198 invalidTimeoutLine); 199 200 File archive = createArchive("bugreport", lines, null); 201 202 String zip = archive.getName(); 203 204 BugReportDurationLines filtered = helper.extractAndFilterBugReport(zip); 205 assertTrue(filtered.contains(dumpstateLine1)); 206 assertTrue(filtered.contains(dumpstateLine2)); 207 assertTrue(filtered.contains(dumpsysLine1)); 208 assertTrue(filtered.contains(dumpsysLine2)); 209 assertTrue(filtered.contains(validTimeoutLine)); 210 assertFalse(filtered.contains(invalidLine)); 211 assertFalse(filtered.contains(showmapLine)); 212 assertFalse(filtered.contains(invalidTimeoutLine)); 213 } 214 215 @Test testExtractAndFilterDumpstateBoard()216 public void testExtractAndFilterDumpstateBoard() throws IOException { 217 String sectionLine1 = "------ Section end: dump_display ------"; 218 String sectionLine2 = "------ Section end: dump_modemlog ------"; 219 String sectionLine3 = "------ Section end: dump_thermal.sh ------"; 220 String durationLine1 = "Elapsed msec: 103"; 221 String durationLine2 = "Elapsed msec: 12532"; 222 String durationLine3 = "Elapsed msec: 7705"; 223 224 // Lines that should be filtered out 225 String invalidLine = "unrelated log line"; 226 List<String> lines = 227 Arrays.asList( 228 sectionLine1, 229 durationLine1, 230 sectionLine2, 231 durationLine2, 232 sectionLine3, 233 durationLine3, 234 invalidLine); 235 236 File archive = createArchive("bugreport", null, lines); 237 238 String zip = archive.getName(); 239 240 DumpstateBoardLines filtered = helper.extractAndFilterDumpstateBoard(zip); 241 assertTrue(filtered.contains(sectionLine1)); 242 assertTrue(filtered.contains(sectionLine2)); 243 assertTrue(filtered.contains(sectionLine3)); 244 assertTrue(filtered.contains(durationLine1)); 245 assertTrue(filtered.contains(durationLine2)); 246 assertTrue(filtered.contains(durationLine3)); 247 assertFalse(filtered.contains(invalidLine)); 248 } 249 250 @Test testParseDecimalDurationForDumpstate()251 public void testParseDecimalDurationForDumpstate() { 252 String line1 = "------ 44.619s was the duration of \'dumpstate_board()\' ------"; 253 String line2 = "------ 21.397s was the duration of \'DUMPSYS\' ------"; 254 // This isn't a "real" case, since parseDecimalDuration() is only ever passed valid lines. 255 String invalidLine = "unrelated log line"; 256 assertEquals(44.619, helper.parseDecimalDuration(line1), DELTA); 257 assertEquals(21.397, helper.parseDecimalDuration(line2), DELTA); 258 assertEquals(-1, helper.parseDecimalDuration(invalidLine), DELTA); 259 } 260 261 @Test testParseDecimalDurationForDumpsys()262 public void testParseDecimalDurationForDumpsys() { 263 String line1 = 264 "--------- 0.051s was the duration of dumpsys SurfaceFlinger, ending at: 2023-04-27" 265 + " 23:50:35"; 266 String line2 = 267 "--------- 24.741s was the duration of dumpsys meminfo, ending at: 2023-04-27" 268 + " 23:51:38"; 269 String line3 = 270 "--------- 0.044s was the duration of dumpsys" 271 + " android.frameworks.stats.IStats/default, ending at: 2023-04-27 23:51:40"; 272 assertEquals(0.051, helper.parseDecimalDuration(line1), DELTA); 273 assertEquals(24.741, helper.parseDecimalDuration(line2), DELTA); 274 assertEquals(0.044, helper.parseDecimalDuration(line3), DELTA); 275 } 276 277 @Test testParseIntegerDurationForDumpstateBoard()278 public void testParseIntegerDurationForDumpstateBoard() { 279 String line1 = "Elapsed msec: 103"; 280 String line2 = "Elapsed msec: 12532"; 281 String line3 = "Elapsed msec: 7705"; 282 assertEquals(103, helper.parseIntegerDuration(line1), DELTA); 283 assertEquals(12532, helper.parseIntegerDuration(line2), DELTA); 284 assertEquals(7705, helper.parseIntegerDuration(line3), DELTA); 285 } 286 287 @Test testParseDumpstateSection()288 public void testParseDumpstateSection() { 289 String line1 = "------ 44.619s was the duration of \'dumpstate_board()\' ------"; 290 String line2 = "------ 21.397s was the duration of \'DUMPSYS\' ------"; 291 // This isn't a "real" case, since parseDumpstateSection() is only ever passed valid lines. 292 String invalidLine = "unrelated log line"; 293 assertEquals("dumpstate_board()", helper.parseDumpstateSection(line1)); 294 assertEquals("DUMPSYS", helper.parseDumpstateSection(line2)); 295 assertNull(helper.parseDumpstateSection(invalidLine)); 296 } 297 298 @Test testParseDumpsysSection()299 public void testParseDumpsysSection() { 300 String line1 = 301 "--------- 0.051s was the duration of dumpsys SurfaceFlinger, ending at: 2023-04-27" 302 + " 23:50:35"; 303 String line2 = 304 "--------- 24.741s was the duration of dumpsys meminfo, ending at: 2023-04-27" 305 + " 23:51:38"; 306 String line3 = 307 "--------- 0.044s was the duration of dumpsys" 308 + " android.frameworks.stats.IStats/default, ending at: 2023-04-27 23:51:40"; 309 assertEquals("SurfaceFlinger", helper.parseDumpsysSection(line1)); 310 assertEquals("meminfo", helper.parseDumpsysSection(line2)); 311 assertEquals("android.frameworks.stats.IStats/default", helper.parseDumpsysSection(line3)); 312 } 313 314 @Test testParseDumpstateBoardSection()315 public void testParseDumpstateBoardSection() { 316 String line1 = "------ Section end: dump_display ------"; 317 String line2 = "------ Section end: dump_modemlog ------"; 318 String line3 = "------ Section end: dump_thermal.sh ------"; 319 assertEquals("dump_display", helper.parseDumpstateBoardSection(line1)); 320 assertEquals("dump_modemlog", helper.parseDumpstateBoardSection(line2)); 321 assertEquals("dump_thermal.sh", helper.parseDumpstateBoardSection(line3)); 322 } 323 324 @Test testConvertDumpstateSectionToKey()325 public void testConvertDumpstateSectionToKey() { 326 String dumpstate1 = "PROCRANK"; 327 String dumpstate2 = "PROCESSES AND THREADS"; 328 String dumpstate3 = "dumpstate_board()"; 329 assertEquals( 330 "bugreport-duration-procrank", helper.convertDumpstateSectionToKey(dumpstate1)); 331 assertEquals( 332 "bugreport-duration-processes-and-threads", 333 helper.convertDumpstateSectionToKey(dumpstate2)); 334 assertEquals( 335 "bugreport-duration-dumpstate_board()", 336 helper.convertDumpstateSectionToKey(dumpstate3)); 337 } 338 339 @Test testConvertDumpsysSectionToKey()340 public void testConvertDumpsysSectionToKey() { 341 String dumpsys1 = "SurfaceFlinger"; 342 String dumpsys2 = "meminfo"; 343 String dumpsys3 = "android.frameworks.stats.IStats/default"; 344 assertEquals( 345 "bugreport-dumpsys-duration-SurfaceFlinger", 346 helper.convertDumpsysSectionToKey(dumpsys1)); 347 assertEquals( 348 "bugreport-dumpsys-duration-meminfo", helper.convertDumpsysSectionToKey(dumpsys2)); 349 assertEquals( 350 "bugreport-dumpsys-duration-android.frameworks.stats.IStats/default", 351 helper.convertDumpsysSectionToKey(dumpsys3)); 352 } 353 354 @Test testConvertDumpstateBoardSectionToKey()355 public void testConvertDumpstateBoardSectionToKey() { 356 String dumpstateBoard1 = "dump_display"; 357 String dumpstateBoard2 = "dump_modemlog"; 358 String dumpstateBoard3 = "dump_thermal.sh"; 359 assertEquals( 360 "bugreport-dumpstate_board-duration-dump_display", 361 helper.convertDumpstateBoardSectionToKey(dumpstateBoard1)); 362 assertEquals( 363 "bugreport-dumpstate_board-duration-dump_modemlog", 364 helper.convertDumpstateBoardSectionToKey(dumpstateBoard2)); 365 assertEquals( 366 "bugreport-dumpstate_board-duration-dump_thermal.sh", 367 helper.convertDumpstateBoardSectionToKey(dumpstateBoard3)); 368 } 369 } 370