1 /* 2 * Copyright (C) 2019 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.tradefed.device.metric; 17 18 import static org.mockito.ArgumentMatchers.any; 19 import static org.mockito.ArgumentMatchers.anyLong; 20 import static org.mockito.ArgumentMatchers.argThat; 21 import static org.mockito.ArgumentMatchers.eq; 22 import static org.mockito.Mockito.doReturn; 23 import static org.mockito.Mockito.mock; 24 import static org.mockito.Mockito.times; 25 import static org.mockito.Mockito.verify; 26 import static org.mockito.Mockito.when; 27 import static org.mockito.MockitoAnnotations.initMocks; 28 29 import com.android.os.AtomsProto.AppCrashOccurred; 30 import com.android.os.AtomsProto.Atom; 31 import com.android.os.StatsLog.EventMetricData; 32 import com.android.os.StatsLog.StatsdStatsReport; 33 import com.android.tradefed.device.ITestDevice; 34 import com.android.tradefed.device.TestDeviceState; 35 import com.android.tradefed.invoker.IInvocationContext; 36 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 37 import com.android.tradefed.result.ITestInvocationListener; 38 39 import org.junit.Assert; 40 import org.junit.Before; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 import org.junit.runners.JUnit4; 44 import org.mockito.ArgumentMatchers; 45 import org.mockito.Mock; 46 import org.mockito.Spy; 47 48 import java.util.ArrayList; 49 import java.util.Arrays; 50 import java.util.Date; 51 import java.util.HashMap; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.concurrent.TimeUnit; 55 import java.util.stream.Collectors; 56 57 /** Unit tests for {@link RuntimeRestartCollector}. */ 58 @RunWith(JUnit4.class) 59 public class RuntimeRestartCollectorTest { 60 private static final String DEVICE_SERIAL_1 = "device_serial_1"; 61 private static final String DEVICE_SERIAL_2 = "device_serial_2"; 62 63 private static final long CONFIG_ID_1 = 1; 64 private static final long CONFIG_ID_2 = 2; 65 66 private static final int TIMESTAMP_1_SECS = 1554764010; 67 private static final String TIMESTAMP_1_STR = 68 RuntimeRestartCollector.TIME_FORMATTER.format( 69 new Date(TimeUnit.SECONDS.toMillis(TIMESTAMP_1_SECS))); 70 private static final int TIMESTAMP_2_SECS = 1554764135; 71 private static final String TIMESTAMP_2_STR = 72 RuntimeRestartCollector.TIME_FORMATTER.format( 73 new Date(TimeUnit.SECONDS.toMillis(TIMESTAMP_2_SECS))); 74 75 private static final long UPTIME_1_NANOS = 76 TimeUnit.HOURS.toNanos(1) + TimeUnit.MINUTES.toNanos(2) + TimeUnit.SECONDS.toNanos(3); 77 private static final String UPTIME_1_STR = "01:02:03"; 78 private static final long UPTIME_2_NANOS = 79 TimeUnit.HOURS.toNanos(1) + TimeUnit.MINUTES.toNanos(4) + TimeUnit.SECONDS.toNanos(8); 80 private static final String UPTIME_2_STR = "01:04:08"; 81 82 private static final EventMetricData RUNTIME_RESTART_DATA_1 = 83 EventMetricData.newBuilder() 84 .setElapsedTimestampNanos(UPTIME_1_NANOS) 85 .setAtom( 86 Atom.newBuilder() 87 .setAppCrashOccurred( 88 AppCrashOccurred.newBuilder() 89 .setProcessName( 90 RuntimeRestartCollector 91 .SYSTEM_SERVER_KEYWORD))) 92 .build(); 93 private static final EventMetricData RUNTIME_RESTART_DATA_2 = 94 RUNTIME_RESTART_DATA_1.toBuilder().setElapsedTimestampNanos(UPTIME_2_NANOS).build(); 95 private static final EventMetricData NOT_RUNTIME_RESTART_DATA = 96 EventMetricData.newBuilder() 97 .setElapsedTimestampNanos(111) 98 .setAtom( 99 Atom.newBuilder() 100 .setAppCrashOccurred( 101 AppCrashOccurred.newBuilder() 102 .setProcessName("not_system_server"))) 103 .build(); 104 105 @Spy private RuntimeRestartCollector mCollector; 106 @Mock private IInvocationContext mContext; 107 @Mock private ITestInvocationListener mListener; 108 109 @Before setUp()110 public void setUp() throws Exception { 111 initMocks(this); 112 } 113 114 /** 115 * Test that the collector makes the correct calls to the statsd utilities for a single device. 116 * 117 * <p>During testRunStarted() it should push the config and pull the statsd metadata. During 118 * testRunEnded() it should pull the event metric data, remove the statsd config, and pull the 119 * statsd metadata again. 120 */ 121 @Test testStatsdInteractions_singleDevice()122 public void testStatsdInteractions_singleDevice() throws Exception { 123 ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1); 124 doReturn(Arrays.asList(testDevice)).when(mContext).getDevices(); 125 doReturn(CONFIG_ID_1) 126 .when(mCollector) 127 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any()); 128 doReturn(new ArrayList<EventMetricData>()) 129 .when(mCollector) 130 .getEventMetricData(any(ITestDevice.class), anyLong()); 131 doReturn(StatsdStatsReport.newBuilder().build()) 132 .when(mCollector) 133 .getStatsdMetadata(any(ITestDevice.class)); 134 135 mCollector.init(mContext, mListener); 136 137 mCollector.testRunStarted("test run", 1); 138 verify(mCollector, times(1)) 139 .pushStatsConfig( 140 eq(testDevice), 141 argThat(l -> l.contains(Atom.APP_CRASH_OCCURRED_FIELD_NUMBER))); 142 verify(mCollector, times(1)).getStatsdMetadata(eq(testDevice)); 143 144 mCollector.testRunEnded(0, new HashMap<String, Metric>()); 145 verify(mCollector, times(1)).getEventMetricData(eq(testDevice), eq(CONFIG_ID_1)); 146 verify(mCollector, times(1)).removeConfig(eq(testDevice), eq(CONFIG_ID_1)); 147 verify(mCollector, times(2)).getStatsdMetadata(eq(testDevice)); 148 } 149 150 /** 151 * Test that the collector makes the correct calls to the statsd utilities for multiple devices. 152 * 153 * <p>During testRunStarted() it should push the config and pull the statsd metadata for each 154 * device. During testRunEnded() it should pull the event metric data, remove the statsd config, 155 * and pull the statsd metadata again for each device. 156 */ 157 @Test testStatsdInteractions_multiDevice()158 public void testStatsdInteractions_multiDevice() throws Exception { 159 ITestDevice testDevice1 = mockTestDevice(DEVICE_SERIAL_1); 160 ITestDevice testDevice2 = mockTestDevice(DEVICE_SERIAL_2); 161 doReturn(Arrays.asList(testDevice1, testDevice2)).when(mContext).getDevices(); 162 doReturn(CONFIG_ID_1) 163 .when(mCollector) 164 .pushStatsConfig(eq(testDevice1), ArgumentMatchers.<List<Integer>>any()); 165 doReturn(CONFIG_ID_2) 166 .when(mCollector) 167 .pushStatsConfig(eq(testDevice2), ArgumentMatchers.<List<Integer>>any()); 168 doReturn(new ArrayList<EventMetricData>()) 169 .when(mCollector) 170 .getEventMetricData(any(ITestDevice.class), anyLong()); 171 doReturn(StatsdStatsReport.newBuilder().build()) 172 .when(mCollector) 173 .getStatsdMetadata(any(ITestDevice.class)); 174 175 mCollector.init(mContext, mListener); 176 177 mCollector.testRunStarted("test run", 1); 178 verify(mCollector, times(1)) 179 .pushStatsConfig( 180 eq(testDevice1), 181 argThat(l -> l.contains(Atom.APP_CRASH_OCCURRED_FIELD_NUMBER))); 182 verify(mCollector, times(1)) 183 .pushStatsConfig( 184 eq(testDevice2), 185 argThat(l -> l.contains(Atom.APP_CRASH_OCCURRED_FIELD_NUMBER))); 186 verify(mCollector, times(1)).getStatsdMetadata(eq(testDevice1)); 187 verify(mCollector, times(1)).getStatsdMetadata(eq(testDevice2)); 188 189 mCollector.testRunEnded(0, new HashMap<String, Metric>()); 190 verify(mCollector, times(1)).getEventMetricData(eq(testDevice1), eq(CONFIG_ID_1)); 191 verify(mCollector, times(1)).getEventMetricData(eq(testDevice2), eq(CONFIG_ID_2)); 192 verify(mCollector, times(1)).removeConfig(eq(testDevice1), eq(CONFIG_ID_1)); 193 verify(mCollector, times(1)).removeConfig(eq(testDevice2), eq(CONFIG_ID_2)); 194 verify(mCollector, times(2)).getStatsdMetadata(eq(testDevice1)); 195 verify(mCollector, times(2)).getStatsdMetadata(eq(testDevice2)); 196 } 197 198 /** 199 * Test that the collector collects a count of zero and no timestamps when no runtime restarts 200 * occur during the test run and there were no prior runtime restarts. 201 */ 202 @Test testAddingMetrics_noRuntimeRestart_noPriorRuntimeRestart()203 public void testAddingMetrics_noRuntimeRestart_noPriorRuntimeRestart() throws Exception { 204 ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1); 205 doReturn(Arrays.asList(testDevice)).when(mContext).getDevices(); 206 doReturn(CONFIG_ID_1) 207 .when(mCollector) 208 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any()); 209 doReturn(new ArrayList<EventMetricData>()) 210 .when(mCollector) 211 .getEventMetricData(any(ITestDevice.class), anyLong()); 212 doReturn(StatsdStatsReport.newBuilder().build(), StatsdStatsReport.newBuilder().build()) 213 .when(mCollector) 214 .getStatsdMetadata(any(ITestDevice.class)); 215 216 HashMap<String, Metric> runMetrics = new HashMap<>(); 217 mCollector.init(mContext, mListener); 218 mCollector.testRunStarted("test run", 1); 219 mCollector.testRunEnded(0, runMetrics); 220 221 // A count should always be present, and should be zero in this case. 222 // If a count is not present, .findFirst().get() throws and the test will fail. 223 int count = getCount(runMetrics); 224 Assert.assertEquals(0, count); 225 // No other metric keys should be present as there is no data. 226 ensureNoMetricWithKeySuffix( 227 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_SECS); 228 ensureNoMetricWithKeySuffix( 229 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_FORMATTED); 230 ensureNoMetricWithKeySuffix(runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_NANOS); 231 ensureNoMetricWithKeySuffix( 232 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_FORMATTED); 233 } 234 235 /** 236 * Test that the collector collects a count of zero and no timestamps when no runtime restarts 237 * and there were prior runtime restarts. 238 */ 239 @Test testAddingMetrics_noRuntimeRestart_withPriorRuntimeRestart()240 public void testAddingMetrics_noRuntimeRestart_withPriorRuntimeRestart() throws Exception { 241 ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1); 242 doReturn(Arrays.asList(testDevice)).when(mContext).getDevices(); 243 doReturn(CONFIG_ID_1) 244 .when(mCollector) 245 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any()); 246 doReturn(new ArrayList<EventMetricData>()) 247 .when(mCollector) 248 .getEventMetricData(any(ITestDevice.class), anyLong()); 249 doReturn( 250 StatsdStatsReport.newBuilder() 251 .addAllSystemRestartSec(Arrays.asList(1, 2)) 252 .build(), 253 StatsdStatsReport.newBuilder() 254 .addAllSystemRestartSec(Arrays.asList(1, 2)) 255 .build()) 256 .when(mCollector) 257 .getStatsdMetadata(any(ITestDevice.class)); 258 259 HashMap<String, Metric> runMetrics = new HashMap<>(); 260 mCollector.init(mContext, mListener); 261 mCollector.testRunStarted("test run", 1); 262 mCollector.testRunEnded(0, runMetrics); 263 264 // A count should always be present, and should be zero in this case. 265 // If a count is not present, .findFirst().get() throws and the test will fail. 266 int count = getCount(runMetrics); 267 Assert.assertEquals(0, count); 268 // No other metric keys should be present as there is no data. 269 ensureNoMetricWithKeySuffix( 270 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_SECS); 271 ensureNoMetricWithKeySuffix( 272 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_FORMATTED); 273 ensureNoMetricWithKeySuffix(runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_NANOS); 274 ensureNoMetricWithKeySuffix( 275 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_FORMATTED); 276 } 277 278 /** 279 * Test that the collector collects counts and timestamps correctly when there are runtime 280 * restarts and there were no prior runtime restarts. 281 */ 282 @Test testAddingMetrics_withRuntimeRestart_noPriorRuntimeRestart()283 public void testAddingMetrics_withRuntimeRestart_noPriorRuntimeRestart() throws Exception { 284 ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1); 285 doReturn(Arrays.asList(testDevice)).when(mContext).getDevices(); 286 doReturn(CONFIG_ID_1) 287 .when(mCollector) 288 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any()); 289 doReturn(Arrays.asList(RUNTIME_RESTART_DATA_1, RUNTIME_RESTART_DATA_2)) 290 .when(mCollector) 291 .getEventMetricData(any(ITestDevice.class), anyLong()); 292 doReturn( 293 StatsdStatsReport.newBuilder().build(), 294 StatsdStatsReport.newBuilder() 295 .addAllSystemRestartSec( 296 Arrays.asList(TIMESTAMP_1_SECS, TIMESTAMP_2_SECS)) 297 .build()) 298 .when(mCollector) 299 .getStatsdMetadata(any(ITestDevice.class)); 300 301 HashMap<String, Metric> runMetrics = new HashMap<>(); 302 mCollector.init(mContext, mListener); 303 mCollector.testRunStarted("test run", 1); 304 mCollector.testRunEnded(0, runMetrics); 305 306 // Count should be two. 307 int count = getCount(runMetrics); 308 Assert.assertEquals(2, count); 309 // There should be two timestamps that match TIMESTAMP_1_SECS and TIMESTAMP_2_SECS 310 // respectively. 311 List<Integer> timestampSecs = 312 getIntMetricValuesByKeySuffix( 313 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_SECS); 314 Assert.assertEquals(Arrays.asList(TIMESTAMP_1_SECS, TIMESTAMP_2_SECS), timestampSecs); 315 // There should be two timestamp strings that match TIMESTAMP_1_STR and TIMESTAMP_2_STR 316 // respectively. 317 List<String> timestampStrs = 318 getStringMetricValuesByKeySuffix( 319 runMetrics, 320 RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_FORMATTED); 321 Assert.assertEquals(Arrays.asList(TIMESTAMP_1_STR, TIMESTAMP_2_STR), timestampStrs); 322 // There should be two uptime timestsmps that match UPTIME_1_NANOS and UPTIME_2_NANOS 323 // respectively. 324 List<Long> uptimeNanos = 325 getLongMetricValuesByKeySuffix( 326 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_NANOS); 327 Assert.assertEquals(Arrays.asList(UPTIME_1_NANOS, UPTIME_2_NANOS), uptimeNanos); 328 // There should be two uptime timestamp strings that match UPTIME_1_STR and UPTIME_2_STR 329 // respectively. 330 List<String> uptimeStrs = 331 getStringMetricValuesByKeySuffix( 332 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_FORMATTED); 333 Assert.assertEquals(Arrays.asList(UPTIME_1_STR, UPTIME_2_STR), uptimeStrs); 334 } 335 336 /** 337 * Test that the collector collects counts and metrics correctly when there are runtime restarts 338 * and there were prior runtime restarts. 339 */ 340 @Test testAddingMetrics_withRuntimeRestart_withPriorRuntimeRestart()341 public void testAddingMetrics_withRuntimeRestart_withPriorRuntimeRestart() throws Exception { 342 ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1); 343 doReturn(Arrays.asList(testDevice)).when(mContext).getDevices(); 344 doReturn(CONFIG_ID_1) 345 .when(mCollector) 346 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any()); 347 doReturn(Arrays.asList(RUNTIME_RESTART_DATA_1, RUNTIME_RESTART_DATA_2)) 348 .when(mCollector) 349 .getEventMetricData(any(ITestDevice.class), anyLong()); 350 doReturn( 351 StatsdStatsReport.newBuilder() 352 .addAllSystemRestartSec(Arrays.asList(1, 2, 3)) 353 .build(), 354 StatsdStatsReport.newBuilder() 355 .addAllSystemRestartSec( 356 Arrays.asList(2, 3, TIMESTAMP_1_SECS, TIMESTAMP_2_SECS)) 357 .build()) 358 .when(mCollector) 359 .getStatsdMetadata(any(ITestDevice.class)); 360 361 HashMap<String, Metric> runMetrics = new HashMap<>(); 362 mCollector.init(mContext, mListener); 363 mCollector.testRunStarted("test run", 1); 364 mCollector.testRunEnded(0, runMetrics); 365 366 // Count should be two. 367 int count = getCount(runMetrics); 368 Assert.assertEquals(2, count); 369 // There should be two timestamps that match TIMESTAMP_1_SECS and TIMESTAMP_2_SECS 370 // respectively. 371 List<Integer> timestampSecs = 372 getIntMetricValuesByKeySuffix( 373 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_SECS); 374 Assert.assertEquals(Arrays.asList(TIMESTAMP_1_SECS, TIMESTAMP_2_SECS), timestampSecs); 375 // There should be two timestamp strings that match TIMESTAMP_1_STR and TIMESTAMP_2_STR 376 // respectively. 377 List<String> timestampStrs = 378 getStringMetricValuesByKeySuffix( 379 runMetrics, 380 RuntimeRestartCollector.METRIC_SUFFIX_SYSTEM_TIMESTAMP_FORMATTED); 381 Assert.assertEquals(Arrays.asList(TIMESTAMP_1_STR, TIMESTAMP_2_STR), timestampStrs); 382 // There should be two uptime timestsmps that match UPTIME_1_NANOS and UPTIME_2_NANOS 383 // respectively. 384 List<Long> uptimeNanos = 385 getLongMetricValuesByKeySuffix( 386 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_NANOS); 387 Assert.assertEquals(Arrays.asList(UPTIME_1_NANOS, UPTIME_2_NANOS), uptimeNanos); 388 // There should be two uptime timestamp strings that match UPTIME_1_STR and UPTIME_2_STR 389 // respectively. 390 List<String> uptimeStrs = 391 getStringMetricValuesByKeySuffix( 392 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_FORMATTED); 393 Assert.assertEquals(Arrays.asList(UPTIME_1_STR, UPTIME_2_STR), uptimeStrs); 394 } 395 396 /** 397 * Test that the {@link AppCrashOccurred}-based collection only collects runtime restarts (i.e. 398 * system server crashes) and not other crashes. 399 */ 400 @Test testAddingMetrics_withRuntimeRestart_reportsSystemServerCrashesOnly()401 public void testAddingMetrics_withRuntimeRestart_reportsSystemServerCrashesOnly() 402 throws Exception { 403 ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1); 404 doReturn(Arrays.asList(testDevice)).when(mContext).getDevices(); 405 doReturn(CONFIG_ID_1) 406 .when(mCollector) 407 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any()); 408 doReturn( 409 Arrays.asList( 410 RUNTIME_RESTART_DATA_1, 411 NOT_RUNTIME_RESTART_DATA, 412 RUNTIME_RESTART_DATA_2)) 413 .when(mCollector) 414 .getEventMetricData(any(ITestDevice.class), anyLong()); 415 doReturn( 416 StatsdStatsReport.newBuilder() 417 .addAllSystemRestartSec(Arrays.asList(1, 2, 3)) 418 .build(), 419 StatsdStatsReport.newBuilder() 420 .addAllSystemRestartSec( 421 Arrays.asList(2, 3, TIMESTAMP_1_SECS, TIMESTAMP_2_SECS)) 422 .build()) 423 .when(mCollector) 424 .getStatsdMetadata(any(ITestDevice.class)); 425 426 HashMap<String, Metric> runMetrics = new HashMap<>(); 427 mCollector.init(mContext, mListener); 428 mCollector.testRunStarted("test run", 1); 429 mCollector.testRunEnded(0, runMetrics); 430 431 // We only check for the uptime timestamps coming from the AppCrashOccurred atoms. 432 433 // There should be two uptime timestamps that match UPTIME_1_NANOS and UPTIME_2_NANOS 434 // respectively. 435 List<Long> uptimeNanos = 436 getLongMetricValuesByKeySuffix( 437 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_NANOS); 438 Assert.assertEquals(Arrays.asList(UPTIME_1_NANOS, UPTIME_2_NANOS), uptimeNanos); 439 // There should be two uptime timestamp strings that match UPTIME_1_STR and UPTIME_2_STR 440 // respectively. 441 List<String> uptimeStrs = 442 getStringMetricValuesByKeySuffix( 443 runMetrics, RuntimeRestartCollector.METRIC_SUFFIX_UPTIME_FORMATTED); 444 Assert.assertEquals(Arrays.asList(UPTIME_1_STR, UPTIME_2_STR), uptimeStrs); 445 } 446 447 /** 448 * Test that the collector reports counts based on the {@link StatsdStatsReport} results when it 449 * disagrees with info from the {@link AppCrashOccurred} atom. 450 */ 451 @Test testAddingMetrics_withRuntimeRestart_useStatsdMetadataResultsForCount()452 public void testAddingMetrics_withRuntimeRestart_useStatsdMetadataResultsForCount() 453 throws Exception { 454 ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1); 455 doReturn(Arrays.asList(testDevice)).when(mContext).getDevices(); 456 // Two data points from the AppCrashOccurred data. 457 doReturn(CONFIG_ID_1) 458 .when(mCollector) 459 .pushStatsConfig(any(ITestDevice.class), ArgumentMatchers.<List<Integer>>any()); 460 doReturn(Arrays.asList(RUNTIME_RESTART_DATA_1, RUNTIME_RESTART_DATA_2)) 461 .when(mCollector) 462 .getEventMetricData(any(ITestDevice.class), anyLong()); 463 // Data from statsd metadata only has one timestamp. 464 doReturn( 465 StatsdStatsReport.newBuilder() 466 .addAllSystemRestartSec(Arrays.asList()) 467 .build(), 468 StatsdStatsReport.newBuilder() 469 .addAllSystemRestartSec(Arrays.asList(TIMESTAMP_1_SECS)) 470 .build()) 471 .when(mCollector) 472 .getStatsdMetadata(any(ITestDevice.class)); 473 474 HashMap<String, Metric> runMetrics = new HashMap<>(); 475 mCollector.init(mContext, mListener); 476 mCollector.testRunStarted("test run", 1); 477 mCollector.testRunEnded(0, runMetrics); 478 479 // Count should be two as in the stubbed EventMetricDataResults, even though statsd metadata 480 // only reported one timestamp. 481 int count = getCount(runMetrics); 482 Assert.assertEquals(1, count); 483 } 484 485 /** 486 * Test that the device serial number is included in the metric key when multiple devices are 487 * under test. 488 */ 489 @Test testAddingMetrics_includesSerialForMultipleDevices()490 public void testAddingMetrics_includesSerialForMultipleDevices() throws Exception { 491 ITestDevice testDevice1 = mockTestDevice(DEVICE_SERIAL_1); 492 ITestDevice testDevice2 = mockTestDevice(DEVICE_SERIAL_2); 493 doReturn(Arrays.asList(testDevice1, testDevice2)).when(mContext).getDevices(); 494 doReturn(CONFIG_ID_1) 495 .when(mCollector) 496 .pushStatsConfig(eq(testDevice1), ArgumentMatchers.<List<Integer>>any()); 497 doReturn(CONFIG_ID_2) 498 .when(mCollector) 499 .pushStatsConfig(eq(testDevice2), ArgumentMatchers.<List<Integer>>any()); 500 doReturn(new ArrayList<EventMetricData>()) 501 .when(mCollector) 502 .getEventMetricData(any(ITestDevice.class), anyLong()); 503 doReturn(StatsdStatsReport.newBuilder().build()) 504 .when(mCollector) 505 .getStatsdMetadata(any(ITestDevice.class)); 506 507 HashMap<String, Metric> runMetrics = new HashMap<>(); 508 mCollector.init(mContext, mListener); 509 mCollector.testRunStarted("test run", 1); 510 mCollector.testRunEnded(0, runMetrics); 511 512 // Check that the count metrics contain the serial numbers for the two devices, 513 // respectively. 514 List<String> countMetricKeys = 515 runMetrics 516 .keySet() 517 .stream() 518 .filter( 519 key -> 520 hasPrefixAndSuffix( 521 key, 522 RuntimeRestartCollector.METRIC_PREFIX, 523 RuntimeRestartCollector.METRIC_SUFFIX_COUNT)) 524 .collect(Collectors.toList()); 525 // One key for each device. 526 Assert.assertEquals(2, countMetricKeys.size()); 527 Assert.assertTrue( 528 countMetricKeys 529 .stream() 530 .anyMatch( 531 key -> 532 (key.contains(DEVICE_SERIAL_1) 533 && !key.contains(DEVICE_SERIAL_2)))); 534 Assert.assertTrue( 535 countMetricKeys 536 .stream() 537 .anyMatch( 538 key -> 539 (key.contains(DEVICE_SERIAL_2) 540 && !key.contains(DEVICE_SERIAL_1)))); 541 } 542 543 /** Helper method to get count from metrics. */ getCount(Map<String, Metric> metrics)544 private static int getCount(Map<String, Metric> metrics) { 545 return Integer.parseInt( 546 metrics.entrySet() 547 .stream() 548 .filter( 549 entry -> 550 hasPrefixAndSuffix( 551 entry.getKey(), 552 RuntimeRestartCollector.METRIC_PREFIX, 553 RuntimeRestartCollector.METRIC_SUFFIX_COUNT)) 554 .map(entry -> entry.getValue()) 555 .findFirst() 556 .get() 557 .getMeasurements() 558 .getSingleString()); 559 } 560 561 /** Helper method to check that no metric key with the given suffix is in the metrics. */ ensureNoMetricWithKeySuffix(Map<String, Metric> metrics, String suffix)562 private static void ensureNoMetricWithKeySuffix(Map<String, Metric> metrics, String suffix) { 563 Assert.assertTrue( 564 metrics.keySet() 565 .stream() 566 .noneMatch( 567 key -> 568 hasPrefixAndSuffix( 569 key, 570 RuntimeRestartCollector.METRIC_PREFIX, 571 suffix))); 572 } 573 574 /** 575 * Helper method to find a comma-separated String metric value by its key suffix, and return its 576 * values as a list. 577 */ getStringMetricValuesByKeySuffix( Map<String, Metric> metrics, String suffix)578 private static List<String> getStringMetricValuesByKeySuffix( 579 Map<String, Metric> metrics, String suffix) { 580 return metrics.entrySet() 581 .stream() 582 .filter( 583 entry -> 584 hasPrefixAndSuffix( 585 entry.getKey(), 586 RuntimeRestartCollector.METRIC_PREFIX, 587 suffix)) 588 .map(entry -> entry.getValue().getMeasurements().getSingleString().split(",")) 589 .flatMap(arr -> Arrays.stream(arr)) 590 .collect(Collectors.toList()); 591 } 592 593 /** 594 * Helper method to find a comma-separated int metric value by its key suffix, and return its 595 * values as a list. 596 */ getIntMetricValuesByKeySuffix( Map<String, Metric> metrics, String suffix)597 private static List<Integer> getIntMetricValuesByKeySuffix( 598 Map<String, Metric> metrics, String suffix) { 599 return getStringMetricValuesByKeySuffix(metrics, suffix) 600 .stream() 601 .map(val -> Integer.valueOf(val)) 602 .collect(Collectors.toList()); 603 } 604 605 /** 606 * Helper method to find a comma-separated long metric value by its key suffix, and return its 607 * values as a list. 608 */ getLongMetricValuesByKeySuffix( Map<String, Metric> metrics, String suffix)609 private static List<Long> getLongMetricValuesByKeySuffix( 610 Map<String, Metric> metrics, String suffix) { 611 return getStringMetricValuesByKeySuffix(metrics, suffix) 612 .stream() 613 .map(val -> Long.valueOf(val)) 614 .collect(Collectors.toList()); 615 } 616 hasPrefixAndSuffix(String target, String prefix, String suffix)617 private static boolean hasPrefixAndSuffix(String target, String prefix, String suffix) { 618 return target.startsWith(prefix) && target.endsWith(suffix); 619 } 620 mockTestDevice(String serial)621 private ITestDevice mockTestDevice(String serial) { 622 ITestDevice device = mock(ITestDevice.class); 623 when(device.getSerialNumber()).thenReturn(serial); 624 when(device.getDeviceState()).thenReturn(TestDeviceState.ONLINE); 625 return device; 626 } 627 } 628