1 /* 2 * Copyright (C) 2021 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 com.android.server.power.hint; 18 19 20 import static com.android.server.power.hint.HintManagerService.CLEAN_UP_UID_DELAY_MILLIS; 21 22 import static com.google.common.truth.Truth.assertThat; 23 24 import static org.junit.Assert.assertArrayEquals; 25 import static org.junit.Assert.assertEquals; 26 import static org.junit.Assert.assertFalse; 27 import static org.junit.Assert.assertNotEquals; 28 import static org.junit.Assert.assertNotNull; 29 import static org.junit.Assert.assertNull; 30 import static org.junit.Assert.assertThrows; 31 import static org.junit.Assert.assertTrue; 32 import static org.mockito.Mockito.any; 33 import static org.mockito.Mockito.anyBoolean; 34 import static org.mockito.Mockito.anyInt; 35 import static org.mockito.Mockito.anyLong; 36 import static org.mockito.Mockito.clearInvocations; 37 import static org.mockito.Mockito.doThrow; 38 import static org.mockito.Mockito.eq; 39 import static org.mockito.Mockito.never; 40 import static org.mockito.Mockito.reset; 41 import static org.mockito.Mockito.times; 42 import static org.mockito.Mockito.verify; 43 import static org.mockito.Mockito.when; 44 45 import android.annotation.NonNull; 46 import android.app.ActivityManager; 47 import android.app.ActivityManagerInternal; 48 import android.content.Context; 49 import android.content.pm.ApplicationInfo; 50 import android.content.pm.PackageManager; 51 import android.hardware.common.fmq.MQDescriptor; 52 import android.hardware.power.ChannelConfig; 53 import android.hardware.power.ChannelMessage; 54 import android.hardware.power.CpuHeadroomParams; 55 import android.hardware.power.CpuHeadroomResult; 56 import android.hardware.power.GpuHeadroomParams; 57 import android.hardware.power.GpuHeadroomResult; 58 import android.hardware.power.IPower; 59 import android.hardware.power.SessionConfig; 60 import android.hardware.power.SessionTag; 61 import android.hardware.power.SupportInfo; 62 import android.hardware.power.WorkDuration; 63 import android.os.Binder; 64 import android.os.CpuHeadroomParamsInternal; 65 import android.os.GpuHeadroomParamsInternal; 66 import android.os.IBinder; 67 import android.os.IHintSession; 68 import android.os.PerformanceHintManager; 69 import android.os.Process; 70 import android.os.RemoteException; 71 import android.os.SessionCreationConfig; 72 import android.platform.test.annotations.RequiresFlagsEnabled; 73 import android.platform.test.flag.junit.CheckFlagsRule; 74 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 75 import android.util.Log; 76 77 import com.android.server.FgThread; 78 import com.android.server.LocalServices; 79 import com.android.server.power.hint.HintManagerService.AppHintSession; 80 import com.android.server.power.hint.HintManagerService.Injector; 81 import com.android.server.power.hint.HintManagerService.NativeWrapper; 82 83 import org.junit.Before; 84 import org.junit.Rule; 85 import org.junit.Test; 86 import org.mockito.Mock; 87 import org.mockito.MockitoAnnotations; 88 import org.mockito.invocation.InvocationOnMock; 89 import org.mockito.stubbing.Answer; 90 91 import java.util.ArrayList; 92 import java.util.Collections; 93 import java.util.List; 94 import java.util.concurrent.CountDownLatch; 95 import java.util.concurrent.TimeUnit; 96 import java.util.concurrent.atomic.AtomicInteger; 97 import java.util.concurrent.atomic.AtomicReference; 98 import java.util.concurrent.locks.LockSupport; 99 100 /** 101 * Tests for {@link com.android.server.power.hint.HintManagerService}. 102 * 103 * Build/Install/Run: 104 * atest FrameworksServicesTests:HintManagerServiceTest 105 */ 106 public class HintManagerServiceTest { 107 private static final String TAG = "HintManagerServiceTest"; 108 makeWorkDuration( long timestamp, long duration, long workPeriodStartTime, long cpuDuration, long gpuDuration)109 private static WorkDuration makeWorkDuration( 110 long timestamp, long duration, long workPeriodStartTime, 111 long cpuDuration, long gpuDuration) { 112 WorkDuration out = new WorkDuration(); 113 out.timeStampNanos = timestamp; 114 out.durationNanos = duration; 115 out.workPeriodStartTimestampNanos = workPeriodStartTime; 116 out.cpuDurationNanos = cpuDuration; 117 out.gpuDurationNanos = gpuDuration; 118 return out; 119 } 120 121 private static final long DEFAULT_HINT_PREFERRED_RATE = 16666666L; 122 private static final long DEFAULT_TARGET_DURATION = 16666666L; 123 private static final long DOUBLED_TARGET_DURATION = 33333333L; 124 private static final long CONCURRENCY_TEST_DURATION_SEC = 10; 125 private static final int UID = Process.myUid(); 126 private static final int TID = Process.myPid(); 127 private static final int TGID = Process.getThreadGroupLeader(TID); 128 private static final int[] SESSION_TIDS_A = new int[] {TID}; 129 private static final int[] SESSION_TIDS_B = new int[] {TID}; 130 private static final int[] SESSION_TIDS_C = new int[] {TID}; 131 private static final long[] DURATIONS_THREE = new long[] {1L, 100L, 1000L}; 132 private static final long[] TIMESTAMPS_THREE = new long[] {1L, 2L, 3L}; 133 private static final long[] SESSION_PTRS = new long[] {11L, 22L, 33L}; 134 private static final long[] SESSION_IDS = new long[] {1L, 11L, 111L}; 135 private static final long[] DURATIONS_ZERO = new long[] {}; 136 private static final long[] TIMESTAMPS_ZERO = new long[] {}; 137 private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L}; 138 private static final WorkDuration[] WORK_DURATIONS_FIVE = new WorkDuration[] { 139 makeWorkDuration(1L, 11L, 1L, 8L, 4L), 140 makeWorkDuration(2L, 13L, 2L, 8L, 6L), 141 makeWorkDuration(3L, 333333333L, 3L, 8L, 333333333L), 142 makeWorkDuration(2L, 13L, 2L, 0L, 6L), 143 makeWorkDuration(2L, 13L, 2L, 8L, 0L), 144 }; 145 private static final String TEST_APP_NAME = "com.android.test.app"; 146 147 @Mock 148 private Context mContext; 149 @Mock 150 private HintManagerService.NativeWrapper mNativeWrapperMock; 151 @Mock 152 private IPower mIPowerMock; 153 @Mock 154 private ActivityManagerInternal mAmInternalMock; 155 @Mock 156 private PackageManager mMockPackageManager; 157 @Rule 158 public final CheckFlagsRule mCheckFlagsRule = 159 DeviceFlagsValueProvider.createCheckFlagsRule(); 160 161 private HintManagerService mService; 162 private ChannelConfig mConfig; 163 private SupportInfo mSupportInfo; 164 fakeCreateWithConfig(Long ptr, Long sessionId)165 private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) { 166 return new Answer<Long>() { 167 public Long answer(InvocationOnMock invocation) { 168 ((SessionConfig) invocation.getArguments()[5]).id = sessionId; 169 return ptr; 170 } 171 }; 172 } 173 174 @Before 175 public void setUp() throws Exception { 176 MockitoAnnotations.initMocks(this); 177 mConfig = new ChannelConfig(); 178 mConfig.readFlagBitmask = 1; 179 mConfig.writeFlagBitmask = 2; 180 mConfig.channelDescriptor = new MQDescriptor<ChannelMessage, Byte>(); 181 mConfig.eventFlagDescriptor = new MQDescriptor<Byte, Byte>(); 182 ApplicationInfo applicationInfo = new ApplicationInfo(); 183 applicationInfo.category = ApplicationInfo.CATEGORY_GAME; 184 mSupportInfo = new SupportInfo(); 185 mSupportInfo.headroom = new SupportInfo.HeadroomSupportInfo(); 186 mSupportInfo.headroom.isCpuSupported = true; 187 mSupportInfo.headroom.cpuMinIntervalMillis = 2000; 188 mSupportInfo.headroom.isGpuSupported = true; 189 mSupportInfo.headroom.gpuMinIntervalMillis = 2000; 190 when(mContext.getPackageManager()).thenReturn(mMockPackageManager); 191 when(mMockPackageManager.getNameForUid(anyInt())).thenReturn(TEST_APP_NAME); 192 when(mMockPackageManager.getApplicationInfo(eq(TEST_APP_NAME), anyInt())) 193 .thenReturn(applicationInfo); 194 when(mNativeWrapperMock.halGetHintSessionPreferredRate()) 195 .thenReturn(DEFAULT_HINT_PREFERRED_RATE); 196 when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), 197 eq(DEFAULT_TARGET_DURATION))).thenReturn(SESSION_PTRS[0]); 198 when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_B), 199 eq(DOUBLED_TARGET_DURATION))).thenReturn(SESSION_PTRS[1]); 200 when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_C), 201 eq(0L))).thenReturn(SESSION_PTRS[2]); 202 when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), 203 eq(SESSION_TIDS_A), eq(DEFAULT_TARGET_DURATION), anyInt(), 204 any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[0], 205 SESSION_IDS[0])); 206 when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), 207 eq(SESSION_TIDS_B), eq(DOUBLED_TARGET_DURATION), anyInt(), 208 any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[1], 209 SESSION_IDS[1])); 210 when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), 211 eq(SESSION_TIDS_C), eq(0L), anyInt(), 212 any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[2], 213 SESSION_IDS[2])); 214 215 when(mIPowerMock.getInterfaceVersion()).thenReturn(6); 216 when(mIPowerMock.getSupportInfo()).thenReturn(mSupportInfo); 217 when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig); 218 LocalServices.removeServiceForTest(ActivityManagerInternal.class); 219 LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock); 220 } 221 222 /** 223 * Mocks the creation calls, but without support for new createHintSessionWithConfig method 224 */ 225 public void makeConfigCreationUnsupported() { 226 reset(mNativeWrapperMock); 227 when(mNativeWrapperMock.halGetHintSessionPreferredRate()) 228 .thenReturn(DEFAULT_HINT_PREFERRED_RATE); 229 when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), 230 eq(DEFAULT_TARGET_DURATION))).thenReturn(SESSION_PTRS[0]); 231 when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_B), 232 eq(DOUBLED_TARGET_DURATION))).thenReturn(SESSION_PTRS[1]); 233 when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_C), 234 eq(0L))).thenReturn(SESSION_PTRS[2]); 235 when(mNativeWrapperMock.halCreateHintSessionWithConfig(anyInt(), anyInt(), 236 any(int[].class), anyLong(), anyInt(), 237 any(SessionConfig.class))).thenThrow(new UnsupportedOperationException()); 238 } 239 240 static class NativeWrapperFake extends NativeWrapper { 241 @Override 242 public void halInit() { 243 } 244 245 @Override 246 public long halGetHintSessionPreferredRate() { 247 return 1; 248 } 249 250 @Override 251 public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) { 252 return 1; 253 } 254 255 @Override 256 public long halCreateHintSessionWithConfig(int tgid, int uid, int[] tids, 257 long durationNanos, int tag, SessionConfig config) { 258 return 1; 259 } 260 261 @Override 262 public void halPauseHintSession(long halPtr) { 263 } 264 265 @Override 266 public void halResumeHintSession(long halPtr) { 267 } 268 269 @Override 270 public void halCloseHintSession(long halPtr) { 271 } 272 273 @Override 274 public void halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos) { 275 } 276 277 @Override 278 public void halReportActualWorkDuration( 279 long halPtr, long[] actualDurationNanos, long[] timeStampNanos) { 280 } 281 282 @Override 283 public void halSendHint(long halPtr, int hint) { 284 } 285 286 @Override 287 public void halSetThreads(long halPtr, int[] tids) { 288 } 289 290 @Override 291 public void halSetMode(long halPtr, int mode, boolean enabled) { 292 } 293 } 294 295 private HintManagerService createService() { 296 mService = new HintManagerService(mContext, new Injector() { 297 NativeWrapper createNativeWrapper() { 298 return mNativeWrapperMock; 299 } 300 IPower createIPower() { 301 return mIPowerMock; 302 } 303 }); 304 return mService; 305 } 306 307 private HintManagerService createServiceWithFakeWrapper() { 308 mService = new HintManagerService(mContext, new Injector() { 309 NativeWrapper createNativeWrapper() { 310 return new NativeWrapperFake(); 311 } 312 IPower createIPower() { 313 return mIPowerMock; 314 } 315 }); 316 return mService; 317 } 318 319 private SessionCreationConfig makeSessionCreationConfig(int[] tids, 320 long targetWorkDurationNanos) { 321 SessionCreationConfig config = new SessionCreationConfig(); 322 config.tids = tids; 323 config.targetWorkDurationNanos = targetWorkDurationNanos; 324 return config; 325 } 326 327 @Test 328 public void testInitializeService() { 329 HintManagerService service = createService(); 330 verify(mNativeWrapperMock).halInit(); 331 assertThat(service.mHintSessionPreferredRate).isEqualTo(DEFAULT_HINT_PREFERRED_RATE); 332 } 333 334 @Test 335 public void testCreateHintSessionInvalidPid() throws Exception { 336 HintManagerService service = createService(); 337 IBinder token = new Binder(); 338 SessionCreationConfig creationConfig = 339 makeSessionCreationConfig(new int[]{TID, 1}, DEFAULT_TARGET_DURATION); 340 // Make sure we throw exception when adding a TID doesn't belong to the processes 341 // In this case, we add `init` PID into the list. 342 SessionConfig config = new SessionConfig(); 343 assertThrows(SecurityException.class, 344 () -> service.getBinderServiceInstance().createHintSessionWithConfig(token, 345 SessionTag.OTHER, creationConfig, config)); 346 } 347 348 @Test 349 public void testCreateHintSessionFallback() throws Exception { 350 HintManagerService service = createService(); 351 IBinder token = new Binder(); 352 makeConfigCreationUnsupported(); 353 SessionCreationConfig creationConfig = 354 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 355 356 IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, 357 SessionTag.OTHER, creationConfig, new SessionConfig()); 358 assertNotNull(a); 359 360 creationConfig.tids = SESSION_TIDS_B; 361 creationConfig.targetWorkDurationNanos = DOUBLED_TARGET_DURATION; 362 IHintSession b = service.getBinderServiceInstance().createHintSessionWithConfig(token, 363 SessionTag.OTHER, creationConfig, new SessionConfig()); 364 assertNotEquals(a, b); 365 366 creationConfig.tids = SESSION_TIDS_C; 367 creationConfig.targetWorkDurationNanos = 0L; 368 IHintSession c = service.getBinderServiceInstance().createHintSessionWithConfig(token, 369 SessionTag.OTHER, creationConfig, new SessionConfig()); 370 assertNotNull(c); 371 verify(mNativeWrapperMock, times(3)).halCreateHintSession(anyInt(), anyInt(), 372 any(int[].class), anyLong()); 373 } 374 375 @Test 376 public void testCreateHintSessionWithConfig() throws Exception { 377 HintManagerService service = createService(); 378 IBinder token = new Binder(); 379 SessionCreationConfig creationConfig = 380 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 381 382 SessionConfig config = new SessionConfig(); 383 IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, 384 SessionTag.OTHER, creationConfig, config); 385 assertNotNull(a); 386 assertEquals(SESSION_IDS[0], config.id); 387 388 SessionConfig config2 = new SessionConfig(); 389 creationConfig.tids = SESSION_TIDS_B; 390 creationConfig.targetWorkDurationNanos = DOUBLED_TARGET_DURATION; 391 IHintSession b = service.getBinderServiceInstance().createHintSessionWithConfig(token, 392 SessionTag.APP, creationConfig, config2); 393 assertNotEquals(a, b); 394 assertEquals(SESSION_IDS[1], config2.id); 395 396 SessionConfig config3 = new SessionConfig(); 397 creationConfig.tids = SESSION_TIDS_C; 398 creationConfig.targetWorkDurationNanos = 0L; 399 IHintSession c = service.getBinderServiceInstance().createHintSessionWithConfig(token, 400 SessionTag.GAME, creationConfig, config3); 401 assertNotNull(c); 402 assertEquals(SESSION_IDS[2], config3.id); 403 verify(mNativeWrapperMock, times(3)).halCreateHintSessionWithConfig(anyInt(), anyInt(), 404 any(int[].class), anyLong(), anyInt(), any(SessionConfig.class)); 405 } 406 407 @Test 408 public void testCreateGraphicsPipelineSessions() throws Exception { 409 HintManagerService service = createService(); 410 IBinder token = new Binder(); 411 412 final int threadCount = 413 service.getBinderServiceInstance().getMaxGraphicsPipelineThreadsCount(); 414 long sessionPtr1 = 1111L; 415 long sessionId1 = 11111L; 416 CountDownLatch stopLatch1 = new CountDownLatch(1); 417 int[] tids1 = createThreads(threadCount, stopLatch1); 418 when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(tids1), 419 eq(DEFAULT_TARGET_DURATION), anyInt(), any(SessionConfig.class))) 420 .thenAnswer(fakeCreateWithConfig(sessionPtr1, sessionId1)); 421 SessionCreationConfig creationConfig = 422 makeSessionCreationConfig(tids1, DEFAULT_TARGET_DURATION); 423 424 creationConfig.modesToEnable = new int[] {1}; // GRAPHICS_PIPELINE 425 426 SessionConfig config = new SessionConfig(); 427 IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, 428 SessionTag.OTHER, creationConfig, config); 429 assertNotNull(a); 430 assertEquals(sessionId1, config.id); 431 432 creationConfig.tids = createThreads(1, stopLatch1); 433 434 assertThrows(IllegalArgumentException.class, () -> { 435 service.getBinderServiceInstance().createHintSessionWithConfig(token, 436 SessionTag.OTHER, creationConfig, config); 437 }); 438 } 439 440 @Test 441 public void testPauseResumeHintSession() throws Exception { 442 HintManagerService service = createService(); 443 IBinder token = new Binder(); 444 SessionCreationConfig creationConfig = 445 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 446 447 AppHintSession a = (AppHintSession) service.getBinderServiceInstance() 448 .createHintSessionWithConfig(token, SessionTag.OTHER, 449 creationConfig, new SessionConfig()); 450 451 // Set session to background and calling updateHintAllowedByProcState() would invoke 452 // pause(); 453 service.mUidObserver.onUidStateChanged( 454 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); 455 456 // Using CountDownLatch to ensure above onUidStateChanged() job was digested. 457 final CountDownLatch latch = new CountDownLatch(1); 458 FgThread.getHandler().post(() -> { 459 latch.countDown(); 460 }); 461 latch.await(); 462 assertFalse(service.mUidObserver.isUidForeground(a.mUid)); 463 verify(mNativeWrapperMock, times(1)).halPauseHintSession(anyLong()); 464 465 // Set session to foreground and calling updateHintAllowedByProcState() would invoke 466 // resume(); 467 service.mUidObserver.onUidStateChanged( 468 a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); 469 470 // Using CountDownLatch to ensure above onUidStateChanged() job was digested. 471 final CountDownLatch latch2 = new CountDownLatch(1); 472 FgThread.getHandler().post(() -> { 473 latch2.countDown(); 474 }); 475 latch2.await(); 476 477 assertTrue(service.mUidObserver.isUidForeground(a.mUid)); 478 verify(mNativeWrapperMock, times(1)).halResumeHintSession(anyLong()); 479 } 480 481 @Test 482 public void testCloseHintSession() throws Exception { 483 HintManagerService service = createService(); 484 IBinder token = new Binder(); 485 SessionCreationConfig creationConfig = 486 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 487 488 IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, 489 SessionTag.OTHER, creationConfig, new SessionConfig()); 490 491 a.close(); 492 verify(mNativeWrapperMock, times(1)).halCloseHintSession(anyLong()); 493 } 494 495 @Test 496 public void testUpdateTargetWorkDuration() throws Exception { 497 HintManagerService service = createService(); 498 IBinder token = new Binder(); 499 SessionCreationConfig creationConfig = 500 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 501 502 IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token, 503 SessionTag.OTHER, creationConfig, new SessionConfig()); 504 505 assertThrows(IllegalArgumentException.class, () -> { 506 a.updateTargetWorkDuration(-1L); 507 }); 508 509 assertThrows(IllegalArgumentException.class, () -> { 510 a.updateTargetWorkDuration(0L); 511 }); 512 513 a.updateTargetWorkDuration(100L); 514 verify(mNativeWrapperMock, times(1)).halUpdateTargetWorkDuration(anyLong(), eq(100L)); 515 } 516 517 @Test 518 public void testReportActualWorkDuration() throws Exception { 519 HintManagerService service = createService(); 520 IBinder token = new Binder(); 521 SessionCreationConfig creationConfig = 522 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 523 524 AppHintSession a = (AppHintSession) service.getBinderServiceInstance() 525 .createHintSessionWithConfig(token, SessionTag.OTHER, 526 creationConfig, new SessionConfig()); 527 528 a.updateTargetWorkDuration(100L); 529 a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE); 530 verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(), 531 eq(DURATIONS_THREE), eq(TIMESTAMPS_THREE)); 532 533 assertThrows(IllegalArgumentException.class, () -> { 534 a.reportActualWorkDuration(DURATIONS_ZERO, TIMESTAMPS_THREE); 535 }); 536 537 assertThrows(IllegalArgumentException.class, () -> { 538 a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_ZERO); 539 }); 540 541 assertThrows(IllegalArgumentException.class, () -> { 542 a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_TWO); 543 }); 544 545 reset(mNativeWrapperMock); 546 // Set session to background, then the duration would not be updated. 547 service.mUidObserver.onUidStateChanged( 548 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); 549 550 // Using CountDownLatch to ensure above onUidStateChanged() job was digested. 551 final CountDownLatch latch = new CountDownLatch(1); 552 FgThread.getHandler().post(() -> { 553 latch.countDown(); 554 }); 555 latch.await(); 556 557 assertFalse(service.mUidObserver.isUidForeground(a.mUid)); 558 a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE); 559 verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any()); 560 } 561 562 @Test 563 public void testSendHint() throws Exception { 564 HintManagerService service = createService(); 565 IBinder token = new Binder(); 566 SessionCreationConfig creationConfig = 567 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 568 569 AppHintSession a = (AppHintSession) service.getBinderServiceInstance() 570 .createHintSessionWithConfig(token, SessionTag.OTHER, 571 creationConfig, new SessionConfig()); 572 573 a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET); 574 verify(mNativeWrapperMock, times(1)).halSendHint(anyLong(), 575 eq(PerformanceHintManager.Session.CPU_LOAD_RESET)); 576 577 assertThrows(IllegalArgumentException.class, () -> { 578 a.sendHint(-1); 579 }); 580 581 reset(mNativeWrapperMock); 582 // Set session to background, then the duration would not be updated. 583 service.mUidObserver.onUidStateChanged( 584 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); 585 FgThread.getHandler().runWithScissors(() -> { }, 500); 586 assertFalse(service.mUidObserver.isUidForeground(a.mUid)); 587 a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET); 588 verify(mNativeWrapperMock, never()).halSendHint(anyLong(), anyInt()); 589 } 590 591 @Test 592 public void testDoHintInBackground() throws Exception { 593 HintManagerService service = createService(); 594 IBinder token = new Binder(); 595 SessionCreationConfig creationConfig = 596 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 597 598 AppHintSession a = (AppHintSession) service.getBinderServiceInstance() 599 .createHintSessionWithConfig(token, SessionTag.OTHER, 600 creationConfig, new SessionConfig()); 601 602 service.mUidObserver.onUidStateChanged( 603 a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); 604 605 // Using CountDownLatch to ensure above onUidStateChanged() job was digested. 606 final CountDownLatch latch = new CountDownLatch(1); 607 FgThread.getHandler().post(() -> { 608 latch.countDown(); 609 }); 610 latch.await(); 611 612 assertFalse(service.mUidObserver.isUidForeground(a.mUid)); 613 } 614 615 @Test 616 public void testDoHintInForeground() throws Exception { 617 HintManagerService service = createService(); 618 IBinder token = new Binder(); 619 SessionCreationConfig creationConfig = 620 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 621 622 AppHintSession a = (AppHintSession) service.getBinderServiceInstance() 623 .createHintSessionWithConfig(token, SessionTag.OTHER, 624 creationConfig, new SessionConfig()); 625 626 service.mUidObserver.onUidStateChanged( 627 a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); 628 assertTrue(service.mUidObserver.isUidForeground(a.mUid)); 629 } 630 631 @Test 632 public void testSetThreads() throws Exception { 633 HintManagerService service = createService(); 634 IBinder token = new Binder(); 635 SessionCreationConfig creationConfig = 636 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 637 638 AppHintSession a = (AppHintSession) service.getBinderServiceInstance() 639 .createHintSessionWithConfig(token, SessionTag.OTHER, 640 creationConfig, new SessionConfig()); 641 642 a.updateTargetWorkDuration(100L); 643 644 assertThrows(IllegalArgumentException.class, () -> { 645 a.setThreads(new int[]{}); 646 }); 647 648 a.setThreads(SESSION_TIDS_B); 649 verify(mNativeWrapperMock, times(1)).halSetThreads(anyLong(), eq(SESSION_TIDS_B)); 650 assertArrayEquals(SESSION_TIDS_B, a.getThreadIds()); 651 652 reset(mNativeWrapperMock); 653 // Set session to background, then the duration would not be updated. 654 service.mUidObserver.onUidStateChanged( 655 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); 656 FgThread.getHandler().runWithScissors(() -> { }, 500); 657 assertFalse(service.mUidObserver.isUidForeground(a.mUid)); 658 a.setThreads(SESSION_TIDS_A); 659 verify(mNativeWrapperMock, never()).halSetThreads(anyLong(), any()); 660 } 661 662 @Test 663 @RequiresFlagsEnabled(Flags.FLAG_POWERHINT_THREAD_CLEANUP) 664 public void testNoCleanupDeadThreadsForPrevPowerHalVersion() throws Exception { 665 reset(mIPowerMock); 666 when(mIPowerMock.getInterfaceVersion()).thenReturn(3); 667 HintManagerService service = createService(); 668 IBinder token = new Binder(); 669 int threadCount = 2; 670 long sessionPtr1 = 111; 671 CountDownLatch stopLatch1 = new CountDownLatch(1); 672 int[] tids1 = createThreads(threadCount, stopLatch1); 673 when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(tids1), 674 eq(DEFAULT_TARGET_DURATION), anyInt(), any(SessionConfig.class))) 675 .thenReturn(sessionPtr1); 676 SessionCreationConfig creationConfig = 677 makeSessionCreationConfig(tids1, DEFAULT_TARGET_DURATION); 678 AppHintSession session1 = (AppHintSession) service.getBinderServiceInstance() 679 .createHintSessionWithConfig(token, SessionTag.OTHER, 680 creationConfig, new SessionConfig()); 681 assertNotNull(session1); 682 683 // trigger UID state change by making the process foreground->background, but because the 684 // power hal version is too low, this should result in no cleanup as setThreads don't fire. 685 service.mUidObserver.onUidStateChanged(UID, 686 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); 687 service.mUidObserver.onUidStateChanged(UID, 688 ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); 689 LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000) + TimeUnit.MILLISECONDS.toNanos( 690 CLEAN_UP_UID_DELAY_MILLIS)); 691 verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any()); 692 reset(mNativeWrapperMock); 693 // this should resume but not update the threads as no cleanup was performed 694 service.mUidObserver.onUidStateChanged(UID, 695 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); 696 verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any()); 697 } 698 699 700 @Test 701 @RequiresFlagsEnabled(Flags.FLAG_POWERHINT_THREAD_CLEANUP) 702 public void testCleanupDeadThreads() throws Exception { 703 HintManagerService service = createService(); 704 IBinder token = new Binder(); 705 int threadCount = 2; 706 707 long sessionPtr1 = 111; 708 CountDownLatch stopLatch1 = new CountDownLatch(1); 709 int[] tids1 = createThreads(threadCount, stopLatch1); 710 when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(tids1), 711 eq(DEFAULT_TARGET_DURATION), anyInt(), any(SessionConfig.class))) 712 .thenReturn(sessionPtr1); 713 SessionCreationConfig creationConfig = 714 makeSessionCreationConfig(tids1, DEFAULT_TARGET_DURATION); 715 AppHintSession session1 = (AppHintSession) service.getBinderServiceInstance() 716 .createHintSessionWithConfig(token, SessionTag.OTHER, 717 creationConfig, new SessionConfig()); 718 assertNotNull(session1); 719 720 // let all session 1 threads to exit and the cleanup should force pause the session 1 721 stopLatch1.countDown(); 722 LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100)); 723 service.mUidObserver.onUidStateChanged(UID, 724 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); 725 service.mUidObserver.onUidStateChanged(UID, 726 ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); 727 LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000) + TimeUnit.MILLISECONDS.toNanos( 728 CLEAN_UP_UID_DELAY_MILLIS)); 729 verify(mNativeWrapperMock, times(1)).halPauseHintSession(eq(sessionPtr1)); 730 verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any()); 731 verifyAllHintsEnabled(session1, false); 732 service.mUidObserver.onUidStateChanged(UID, 733 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); 734 LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000)); 735 assertArrayEquals(tids1, session1.getTidsInternal()); 736 verifyAllHintsEnabled(session1, false); 737 reset(mNativeWrapperMock); 738 739 // in foreground, set new tids for session 1 then it should be resumed and all hints allowed 740 stopLatch1 = new CountDownLatch(1); 741 tids1 = createThreads(threadCount, stopLatch1); 742 session1.setThreads(tids1); 743 verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr1), eq(tids1)); 744 verify(mNativeWrapperMock, times(1)).halResumeHintSession(eq(sessionPtr1)); 745 verifyAllHintsEnabled(session1, true); 746 reset(mNativeWrapperMock); 747 748 // let all session 1 and 2 non isolated threads to exit 749 stopLatch1.countDown(); 750 LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100)); 751 service.mUidObserver.onUidStateChanged(UID, 752 ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); 753 LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000) + TimeUnit.MILLISECONDS.toNanos( 754 CLEAN_UP_UID_DELAY_MILLIS)); 755 verify(mNativeWrapperMock, times(1)).halPauseHintSession(eq(sessionPtr1)); 756 verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any()); 757 // in background, set threads for session 1 then it should not be force paused next time 758 session1.setThreads(SESSION_TIDS_A); 759 // the new TIDs pending list should be updated 760 assertArrayEquals(SESSION_TIDS_A, session1.getTidsInternal()); 761 verifyAllHintsEnabled(session1, false); 762 reset(mNativeWrapperMock); 763 764 service.mUidObserver.onUidStateChanged(UID, 765 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); 766 LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000) + TimeUnit.MILLISECONDS.toNanos( 767 CLEAN_UP_UID_DELAY_MILLIS)); 768 verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr1), 769 eq(SESSION_TIDS_A)); 770 verifyAllHintsEnabled(session1, true); 771 } 772 773 private void verifyAllHintsEnabled(AppHintSession session, boolean verifyEnabled) { 774 session.reportActualWorkDuration2(new WorkDuration[]{makeWorkDuration(1, 3, 2, 1, 1000)}); 775 session.reportActualWorkDuration(new long[]{1}, new long[]{2}); 776 session.updateTargetWorkDuration(3); 777 session.setMode(0, true); 778 session.sendHint(1); 779 if (verifyEnabled) { 780 verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration( 781 eq(session.mHalSessionPtr), any()); 782 verify(mNativeWrapperMock, times(1)).halSetMode(eq(session.mHalSessionPtr), anyInt(), 783 anyBoolean()); 784 verify(mNativeWrapperMock, times(1)).halUpdateTargetWorkDuration( 785 eq(session.mHalSessionPtr), anyLong()); 786 verify(mNativeWrapperMock, times(1)).halSendHint(eq(session.mHalSessionPtr), anyInt()); 787 } else { 788 verify(mNativeWrapperMock, never()).halReportActualWorkDuration( 789 eq(session.mHalSessionPtr), any()); 790 verify(mNativeWrapperMock, never()).halSetMode(eq(session.mHalSessionPtr), anyInt(), 791 anyBoolean()); 792 verify(mNativeWrapperMock, never()).halUpdateTargetWorkDuration( 793 eq(session.mHalSessionPtr), anyLong()); 794 verify(mNativeWrapperMock, never()).halSendHint(eq(session.mHalSessionPtr), anyInt()); 795 } 796 } 797 798 private int[] createThreads(int threadCount, CountDownLatch stopLatch) 799 throws InterruptedException { 800 int[] tids = new int[threadCount]; 801 AtomicInteger k = new AtomicInteger(0); 802 CountDownLatch latch = new CountDownLatch(threadCount); 803 for (int j = 0; j < threadCount; j++) { 804 Thread thread = new Thread(() -> { 805 try { 806 tids[k.getAndIncrement()] = android.os.Process.myTid(); 807 latch.countDown(); 808 stopLatch.await(); 809 } catch (InterruptedException e) { 810 throw new RuntimeException(e); 811 } 812 }); 813 thread.start(); 814 } 815 latch.await(); 816 return tids; 817 } 818 819 @Test 820 public void testSetMode() throws Exception { 821 HintManagerService service = createService(); 822 IBinder token = new Binder(); 823 SessionCreationConfig creationConfig = 824 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 825 826 AppHintSession a = (AppHintSession) service.getBinderServiceInstance() 827 .createHintSessionWithConfig(token, SessionTag.OTHER, 828 creationConfig, new SessionConfig()); 829 830 a.setMode(0, true); 831 verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(), 832 eq(0), eq(true)); 833 834 a.setMode(0, false); 835 verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(), 836 eq(0), eq(false)); 837 } 838 839 @Test 840 public void testSetModeSessionInBackGround() throws Exception { 841 HintManagerService service = createService(); 842 IBinder token = new Binder(); 843 SessionCreationConfig creationConfig = 844 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 845 846 AppHintSession a = (AppHintSession) service.getBinderServiceInstance() 847 .createHintSessionWithConfig(token, SessionTag.OTHER, 848 creationConfig, new SessionConfig()); 849 850 // Set session to background, then the duration would not be updated. 851 service.mUidObserver.onUidStateChanged( 852 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); 853 FgThread.getHandler().runWithScissors(() -> { 854 }, 500); 855 assertFalse(service.mUidObserver.isUidForeground(a.mUid)); 856 a.setMode(0, true); 857 verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean()); 858 } 859 860 @Test 861 public void testSetModeInvalid() throws Exception { 862 HintManagerService service = createService(); 863 IBinder token = new Binder(); 864 SessionCreationConfig creationConfig = 865 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 866 867 AppHintSession a = (AppHintSession) service.getBinderServiceInstance() 868 .createHintSessionWithConfig(token, SessionTag.OTHER, 869 creationConfig, new SessionConfig()); 870 871 assertThrows(IllegalArgumentException.class, () -> { 872 a.setMode(-1, true); 873 }); 874 } 875 876 @Test 877 public void testSetModeUponSessionCreation() throws Exception { 878 HintManagerService service = createService(); 879 IBinder token = new Binder(); 880 SessionCreationConfig creationConfig = 881 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 882 creationConfig.modesToEnable = new int[] {0, 1}; 883 884 AppHintSession a = (AppHintSession) service.getBinderServiceInstance() 885 .createHintSessionWithConfig(token, SessionTag.OTHER, 886 creationConfig, new SessionConfig()); 887 assertNotNull(a); 888 verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(), 889 eq(0), eq(true)); 890 verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(), 891 eq(1), eq(true)); 892 } 893 894 @Test 895 public void testGetChannel() throws Exception { 896 HintManagerService service = createService(); 897 Binder token = new Binder(); 898 899 // Should only call once, after caching the first call 900 ChannelConfig config = service.getBinderServiceInstance().getSessionChannel(token); 901 ChannelConfig config2 = service.getBinderServiceInstance().getSessionChannel(token); 902 verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); 903 assertEquals(config.readFlagBitmask, mConfig.readFlagBitmask); 904 assertEquals(config.writeFlagBitmask, mConfig.writeFlagBitmask); 905 assertEquals(config2.readFlagBitmask, mConfig.readFlagBitmask); 906 assertEquals(config2.writeFlagBitmask, mConfig.writeFlagBitmask); 907 } 908 909 @Test 910 public void testGetChannelTwice() throws Exception { 911 HintManagerService service = createService(); 912 Binder token = new Binder(); 913 914 service.getBinderServiceInstance().getSessionChannel(token); 915 verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); 916 service.getBinderServiceInstance().closeSessionChannel(); 917 verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); 918 919 clearInvocations(mIPowerMock); 920 921 service.getBinderServiceInstance().getSessionChannel(token); 922 verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); 923 service.getBinderServiceInstance().closeSessionChannel(); 924 verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); 925 } 926 927 @Test 928 public void testGetChannelFails() throws Exception { 929 HintManagerService service = createService(); 930 Binder token = new Binder(); 931 932 when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenThrow(RemoteException.class); 933 934 assertThrows(IllegalStateException.class, () -> { 935 service.getBinderServiceInstance().getSessionChannel(token); 936 }); 937 } 938 939 940 @Test 941 public void testGetChannelBadVersion() throws Exception { 942 when(mIPowerMock.getInterfaceVersion()).thenReturn(3); 943 HintManagerService service = createService(); 944 Binder token = new Binder(); 945 946 reset(mIPowerMock); 947 when(mIPowerMock.getInterfaceVersion()).thenReturn(3); 948 when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig); 949 950 ChannelConfig channel = service.getBinderServiceInstance().getSessionChannel(token); 951 verify(mIPowerMock, times(0)).getSessionChannel(eq(TGID), eq(UID)); 952 assertNull(channel); 953 } 954 955 @Test 956 public void testCloseChannel() throws Exception { 957 HintManagerService service = createService(); 958 Binder token = new Binder(); 959 960 service.getBinderServiceInstance().getSessionChannel(token); 961 service.getBinderServiceInstance().closeSessionChannel(); 962 verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); 963 } 964 965 @Test 966 public void testCloseChannelFails() throws Exception { 967 HintManagerService service = createService(); 968 Binder token = new Binder(); 969 970 service.getBinderServiceInstance().getSessionChannel(token); 971 972 doThrow(RemoteException.class).when(mIPowerMock).closeSessionChannel(anyInt(), anyInt()); 973 974 assertThrows(IllegalStateException.class, () -> { 975 service.getBinderServiceInstance().closeSessionChannel(); 976 }); 977 } 978 979 @Test 980 public void testDoubleClose() throws Exception { 981 HintManagerService service = createService(); 982 Binder token = new Binder(); 983 984 service.getBinderServiceInstance().getSessionChannel(token); 985 service.getBinderServiceInstance().closeSessionChannel(); 986 service.getBinderServiceInstance().closeSessionChannel(); 987 verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); 988 } 989 990 // This test checks that concurrent operations from different threads on IHintService, 991 // IHintSession and UidObserver will not cause data race or deadlock. Ideally we should also 992 // check the output of threads' reportActualDuration performance to detect lock starvation 993 // but the result is not stable, so it's better checked manually. 994 @Test 995 public void testConcurrency() throws Exception { 996 HintManagerService service = createServiceWithFakeWrapper(); 997 // initialize session threads to run in parallel 998 final int sessionCount = 10; 999 // the signal that the main thread will send to session threads to check for run or exit 1000 AtomicReference<Boolean> shouldRun = new AtomicReference<>(true); 1001 // the signal for main test thread to wait for session threads and notifier thread to 1002 // finish and exit 1003 CountDownLatch latch = new CountDownLatch(sessionCount + 1); 1004 // list of exceptions with one per session thread or notifier thread 1005 List<AtomicReference<Exception>> execs = new ArrayList<>(sessionCount + 1); 1006 List<Thread> threads = new ArrayList<>(sessionCount + 1); 1007 for (int i = 0; i < sessionCount; i++) { 1008 final AtomicReference<Exception> exec = new AtomicReference<>(); 1009 execs.add(exec); 1010 int j = i; 1011 Thread app = new Thread(() -> { 1012 try { 1013 while (shouldRun.get()) { 1014 runAppHintSession(service, j, shouldRun); 1015 } 1016 } catch (Exception e) { 1017 exec.set(e); 1018 } finally { 1019 latch.countDown(); 1020 } 1021 }); 1022 threads.add(app); 1023 } 1024 1025 // initialize a UID state notifier thread to run in parallel 1026 final AtomicReference<Exception> notifierExec = new AtomicReference<>(); 1027 execs.add(notifierExec); 1028 Thread notifier = new Thread(() -> { 1029 try { 1030 long min = Long.MAX_VALUE; 1031 long max = Long.MIN_VALUE; 1032 long sum = 0; 1033 int count = 0; 1034 while (shouldRun.get()) { 1035 long start = System.nanoTime(); 1036 service.mUidObserver.onUidStateChanged(UID, 1037 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); 1038 long elapsed = System.nanoTime() - start; 1039 sum += elapsed; 1040 count++; 1041 min = Math.min(min, elapsed); 1042 max = Math.max(max, elapsed); 1043 LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500)); 1044 service.mUidObserver.onUidStateChanged(UID, 1045 ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); 1046 // let the cleanup work proceed 1047 LockSupport.parkNanos( 1048 TimeUnit.MILLISECONDS.toNanos(500) + TimeUnit.MILLISECONDS.toNanos( 1049 CLEAN_UP_UID_DELAY_MILLIS)); 1050 } 1051 Log.d(TAG, "notifier thread min " + min + " max " + max + " avg " + sum / count); 1052 service.mUidObserver.onUidGone(UID, true); 1053 } catch (Exception e) { 1054 notifierExec.set(e); 1055 } finally { 1056 latch.countDown(); 1057 } 1058 }); 1059 threads.add(notifier); 1060 1061 // start all the threads 1062 for (Thread thread : threads) { 1063 thread.start(); 1064 } 1065 // keep the test running for a few seconds 1066 LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(CONCURRENCY_TEST_DURATION_SEC)); 1067 // send signal to stop all threads 1068 shouldRun.set(false); 1069 // wait for all threads to exit 1070 latch.await(); 1071 // check if any thread throws exception 1072 for (AtomicReference<Exception> exec : execs) { 1073 if (exec.get() != null) { 1074 throw exec.get(); 1075 } 1076 } 1077 } 1078 1079 private void runAppHintSession(HintManagerService service, int logId, 1080 AtomicReference<Boolean> shouldRun) throws Exception { 1081 IBinder token = new Binder(); 1082 SessionCreationConfig creationConfig = 1083 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 1084 1085 AppHintSession a = (AppHintSession) service.getBinderServiceInstance() 1086 .createHintSessionWithConfig(token, SessionTag.OTHER, 1087 creationConfig, new SessionConfig()); 1088 // we will start some threads and get their valid TIDs to update 1089 int threadCount = 3; 1090 // the list of TIDs 1091 int[] tids = new int[threadCount]; 1092 // atomic index for each thread to set its TID in the list 1093 AtomicInteger k = new AtomicInteger(0); 1094 // signal for the session main thread to wait for child threads to finish updating TIDs 1095 CountDownLatch latch = new CountDownLatch(threadCount); 1096 // signal for the session main thread to notify child threads to exit 1097 CountDownLatch stopLatch = new CountDownLatch(1); 1098 for (int j = 0; j < threadCount; j++) { 1099 Thread thread = new Thread(() -> { 1100 try { 1101 tids[k.getAndIncrement()] = android.os.Process.myTid(); 1102 latch.countDown(); 1103 stopLatch.await(); 1104 } catch (InterruptedException e) { 1105 throw new RuntimeException(e); 1106 } 1107 }); 1108 thread.start(); 1109 } 1110 latch.await(); 1111 a.setThreads(tids); 1112 // we don't need the threads to exist after update 1113 stopLatch.countDown(); 1114 a.updateTargetWorkDuration(5); 1115 // measure the time it takes in HintManagerService to run reportActualDuration 1116 long min = Long.MAX_VALUE; 1117 long max = Long.MIN_VALUE; 1118 long sum = 0; 1119 int count = 0; 1120 List<Long> values = new ArrayList<>(); 1121 long testStart = System.nanoTime(); 1122 // run report actual for 4-second per cycle 1123 while (shouldRun.get() && System.nanoTime() - testStart < TimeUnit.SECONDS.toNanos( 1124 Math.min(4, CONCURRENCY_TEST_DURATION_SEC))) { 1125 long start = System.nanoTime(); 1126 a.reportActualWorkDuration(new long[]{5}, new long[]{start}); 1127 long elapsed = System.nanoTime() - start; 1128 values.add(elapsed); 1129 LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(5)); 1130 sum += elapsed; 1131 count++; 1132 min = Math.min(min, elapsed); 1133 max = Math.max(max, elapsed); 1134 } 1135 Collections.sort(values); 1136 if (!values.isEmpty()) { 1137 Log.d(TAG, "app thread " + logId + " min " + min + " max " + max 1138 + " avg " + sum / count + " count " + count 1139 + " 80th " + values.get((int) (values.size() * 0.8)) 1140 + " 90th " + values.get((int) (values.size() * 0.9)) 1141 + " 95th " + values.get((int) (values.size() * 0.95))); 1142 } else { 1143 Log.w(TAG, "No reportActualWorkDuration executed"); 1144 } 1145 a.close(); 1146 } 1147 1148 @Test 1149 public void testReportActualWorkDuration2() throws Exception { 1150 HintManagerService service = createService(); 1151 IBinder token = new Binder(); 1152 SessionCreationConfig creationConfig = 1153 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION); 1154 1155 AppHintSession a = (AppHintSession) service.getBinderServiceInstance() 1156 .createHintSessionWithConfig(token, SessionTag.OTHER, 1157 creationConfig, new SessionConfig()); 1158 1159 a.updateTargetWorkDuration(100L); 1160 a.reportActualWorkDuration2(WORK_DURATIONS_FIVE); 1161 verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(), 1162 eq(WORK_DURATIONS_FIVE)); 1163 1164 assertThrows(IllegalArgumentException.class, () -> { 1165 a.reportActualWorkDuration2(new WorkDuration[] {}); 1166 }); 1167 1168 assertThrows(IllegalArgumentException.class, () -> { 1169 a.reportActualWorkDuration2( 1170 new WorkDuration[] {makeWorkDuration(1L, 11L, -1L, 8L, 4L)}); 1171 }); 1172 1173 assertThrows(IllegalArgumentException.class, () -> { 1174 a.reportActualWorkDuration2(new WorkDuration[] {makeWorkDuration(1L, 0L, 1L, 8L, 4L)}); 1175 }); 1176 1177 assertThrows(IllegalArgumentException.class, () -> { 1178 a.reportActualWorkDuration2(new WorkDuration[] {makeWorkDuration(1L, 11L, 1L, 0L, 0L)}); 1179 }); 1180 1181 assertThrows(IllegalArgumentException.class, () -> { 1182 a.reportActualWorkDuration2( 1183 new WorkDuration[] {makeWorkDuration(1L, 11L, 1L, 8L, -1L)}); 1184 }); 1185 1186 reset(mNativeWrapperMock); 1187 // Set session to background, then the duration would not be updated. 1188 service.mUidObserver.onUidStateChanged( 1189 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); 1190 1191 // Using CountDownLatch to ensure above onUidStateChanged() job was digested. 1192 final CountDownLatch latch = new CountDownLatch(1); 1193 FgThread.getHandler().post(() -> { 1194 latch.countDown(); 1195 }); 1196 latch.await(); 1197 1198 assertFalse(service.mUidObserver.isUidForeground(a.mUid)); 1199 a.reportActualWorkDuration2(WORK_DURATIONS_FIVE); 1200 verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any()); 1201 } 1202 1203 @Test 1204 public void testChannelDiesWhenTokenDies() throws Exception { 1205 HintManagerService service = createService(); 1206 1207 class DyingToken extends Binder { 1208 DeathRecipient mToNotify; 1209 @Override 1210 public void linkToDeath(@NonNull DeathRecipient recipient, int flags) { 1211 mToNotify = recipient; 1212 super.linkToDeath(recipient, flags); 1213 } 1214 1215 public void fakeDeath() { 1216 mToNotify.binderDied(); 1217 } 1218 } 1219 1220 DyingToken token = new DyingToken(); 1221 1222 service.getBinderServiceInstance().getSessionChannel(token); 1223 verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); 1224 assertTrue(service.hasChannel(TGID, UID)); 1225 1226 token.fakeDeath(); 1227 1228 assertFalse(service.hasChannel(TGID, UID)); 1229 verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); 1230 1231 clearInvocations(mIPowerMock); 1232 1233 token = new DyingToken(); 1234 service.getBinderServiceInstance().getSessionChannel(token); 1235 verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); 1236 assertTrue(service.hasChannel(TGID, UID)); 1237 } 1238 1239 @Test 1240 public void testHeadroomPowerHalNotSupported() throws Exception { 1241 when(mIPowerMock.getInterfaceVersion()).thenReturn(5); 1242 HintManagerService service = createService(); 1243 assertThrows(UnsupportedOperationException.class, () -> { 1244 service.getBinderServiceInstance().getCpuHeadroom(null); 1245 }); 1246 assertThrows(UnsupportedOperationException.class, () -> { 1247 service.getBinderServiceInstance().getGpuHeadroom(null); 1248 }); 1249 assertThrows(UnsupportedOperationException.class, () -> { 1250 service.getBinderServiceInstance().getCpuHeadroomMinIntervalMillis(); 1251 }); 1252 assertThrows(UnsupportedOperationException.class, () -> { 1253 service.getBinderServiceInstance().getGpuHeadroomMinIntervalMillis(); 1254 }); 1255 } 1256 1257 @Test 1258 public void testCpuHeadroomCache() throws Exception { 1259 CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal(); 1260 CpuHeadroomParams halParams1 = new CpuHeadroomParams(); 1261 halParams1.calculationType = CpuHeadroomParams.CalculationType.MIN; 1262 halParams1.tids = new int[]{Process.myPid()}; 1263 1264 CpuHeadroomParamsInternal params2 = new CpuHeadroomParamsInternal(); 1265 params2.usesDeviceHeadroom = true; 1266 params2.calculationType = CpuHeadroomParams.CalculationType.MIN; 1267 CpuHeadroomParams halParams2 = new CpuHeadroomParams(); 1268 halParams2.calculationType = CpuHeadroomParams.CalculationType.MIN; 1269 halParams2.tids = new int[]{}; 1270 1271 CpuHeadroomParamsInternal params3 = new CpuHeadroomParamsInternal(); 1272 params3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE; 1273 CpuHeadroomParams halParams3 = new CpuHeadroomParams(); 1274 halParams3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE; 1275 halParams3.tids = new int[]{Process.myPid()}; 1276 1277 // this params should not be cached as the window is not default 1278 CpuHeadroomParamsInternal params4 = new CpuHeadroomParamsInternal(); 1279 params4.calculationWindowMillis = 123; 1280 CpuHeadroomParams halParams4 = new CpuHeadroomParams(); 1281 halParams4.calculationType = CpuHeadroomParams.CalculationType.MIN; 1282 halParams4.calculationWindowMillis = 123; 1283 halParams4.tids = new int[]{Process.myPid()}; 1284 1285 float headroom1 = 0.1f; 1286 CpuHeadroomResult halRet1 = CpuHeadroomResult.globalHeadroom(headroom1); 1287 when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(halRet1); 1288 float headroom2 = 0.2f; 1289 CpuHeadroomResult halRet2 = CpuHeadroomResult.globalHeadroom(headroom2); 1290 when(mIPowerMock.getCpuHeadroom(eq(halParams2))).thenReturn(halRet2); 1291 float headroom3 = 0.3f; 1292 CpuHeadroomResult halRet3 = CpuHeadroomResult.globalHeadroom(headroom3); 1293 when(mIPowerMock.getCpuHeadroom(eq(halParams3))).thenReturn(halRet3); 1294 float headroom4 = 0.4f; 1295 CpuHeadroomResult halRet4 = CpuHeadroomResult.globalHeadroom(headroom4); 1296 when(mIPowerMock.getCpuHeadroom(eq(halParams4))).thenReturn(halRet4); 1297 1298 HintManagerService service = createService(); 1299 clearInvocations(mIPowerMock); 1300 1301 assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1)); 1302 verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1)); 1303 assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2)); 1304 verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2)); 1305 assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3)); 1306 verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3)); 1307 assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4)); 1308 verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4)); 1309 1310 // verify cache is working 1311 clearInvocations(mIPowerMock); 1312 assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1)); 1313 assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2)); 1314 assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3)); 1315 assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4)); 1316 verify(mIPowerMock, times(1)).getCpuHeadroom(any()); 1317 verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1)); 1318 verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams2)); 1319 verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3)); 1320 verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4)); 1321 1322 // after 1 more second it should be served with cache still 1323 Thread.sleep(1000); 1324 clearInvocations(mIPowerMock); 1325 assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1)); 1326 assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2)); 1327 assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3)); 1328 assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4)); 1329 verify(mIPowerMock, times(1)).getCpuHeadroom(any()); 1330 verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1)); 1331 verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams2)); 1332 verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3)); 1333 verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4)); 1334 1335 // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval 1336 Thread.sleep(1100); 1337 clearInvocations(mIPowerMock); 1338 assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1)); 1339 assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2)); 1340 assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3)); 1341 assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4)); 1342 verify(mIPowerMock, times(4)).getCpuHeadroom(any()); 1343 verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1)); 1344 verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2)); 1345 verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3)); 1346 verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4)); 1347 } 1348 1349 @Test 1350 public void testGpuHeadroomCache() throws Exception { 1351 GpuHeadroomParamsInternal params1 = new GpuHeadroomParamsInternal(); 1352 GpuHeadroomParams halParams1 = new GpuHeadroomParams(); 1353 halParams1.calculationType = GpuHeadroomParams.CalculationType.MIN; 1354 1355 GpuHeadroomParamsInternal params2 = new GpuHeadroomParamsInternal(); 1356 params2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE; 1357 params2.calculationWindowMillis = 123; 1358 GpuHeadroomParams halParams2 = new GpuHeadroomParams(); 1359 halParams2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE; 1360 halParams2.calculationWindowMillis = 123; 1361 1362 float headroom1 = 0.1f; 1363 GpuHeadroomResult halRet1 = GpuHeadroomResult.globalHeadroom(headroom1); 1364 when(mIPowerMock.getGpuHeadroom(eq(halParams1))).thenReturn(halRet1); 1365 float headroom2 = 0.2f; 1366 GpuHeadroomResult halRet2 = GpuHeadroomResult.globalHeadroom(headroom2); 1367 when(mIPowerMock.getGpuHeadroom(eq(halParams2))).thenReturn(halRet2); 1368 HintManagerService service = createService(); 1369 clearInvocations(mIPowerMock); 1370 1371 assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1)); 1372 assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2)); 1373 verify(mIPowerMock, times(2)).getGpuHeadroom(any()); 1374 verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1)); 1375 verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2)); 1376 1377 // verify cache is working 1378 clearInvocations(mIPowerMock); 1379 assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1)); 1380 assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2)); 1381 verify(mIPowerMock, times(1)).getGpuHeadroom(any()); 1382 verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1)); 1383 verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2)); 1384 1385 // after 1 more second it should be served with cache still 1386 Thread.sleep(1000); 1387 clearInvocations(mIPowerMock); 1388 assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1)); 1389 assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2)); 1390 verify(mIPowerMock, times(1)).getGpuHeadroom(any()); 1391 verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1)); 1392 verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2)); 1393 1394 // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval 1395 Thread.sleep(1100); 1396 clearInvocations(mIPowerMock); 1397 assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1)); 1398 assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2)); 1399 verify(mIPowerMock, times(2)).getGpuHeadroom(any()); 1400 verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1)); 1401 verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2)); 1402 } 1403 } 1404