ADPF: PerformanceHintManager API implementation

Test: Manual test, run bouncy ball
Test: atest FrameworksCoreTests:android.os.PerformanceHintManagerTest
Bug: 158791282
Change-Id: Ia0bf2b06675b8cf57cdf1668ee90e98026416439
Signed-off-by: Wei Wang <wvw@google.com>
This commit is contained in:
Jimmy Shiu
2021-02-25 14:43:17 +08:00
committed by Wei Wang
parent badb432a45
commit 4c6e5036b7
7 changed files with 528 additions and 0 deletions

View File

@@ -10644,6 +10644,7 @@ package android.content {
field public static final String NOTIFICATION_SERVICE = "notification";
field public static final String NSD_SERVICE = "servicediscovery";
field public static final String PEOPLE_SERVICE = "people";
field public static final String PERFORMANCE_HINT_SERVICE = "performance_hint";
field public static final String POWER_SERVICE = "power";
field public static final String PRINT_SERVICE = "print";
field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1
@@ -31559,6 +31560,17 @@ package android.os {
field public static final int PATTERN_SUFFIX = 4; // 0x4
}
public final class PerformanceHintManager {
method @Nullable public android.os.PerformanceHintManager.Session createHintSession(@NonNull int[], long);
method public long getPreferredUpdateRateNanos();
}
public static class PerformanceHintManager.Session implements java.io.Closeable {
method public void close();
method public void reportActualWorkDuration(long);
method public void updateTargetWorkDuration(long);
}
public final class PersistableBundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
ctor public PersistableBundle();
ctor public PersistableBundle(int);

View File

@@ -158,12 +158,14 @@ import android.os.IBatteryPropertiesRegistrar;
import android.os.IBinder;
import android.os.IDumpstate;
import android.os.IHardwarePropertiesManager;
import android.os.IHintManager;
import android.os.IPowerManager;
import android.os.IRecoverySystem;
import android.os.ISystemUpdateManager;
import android.os.IThermalService;
import android.os.IUserManager;
import android.os.IncidentManager;
import android.os.PerformanceHintManager;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.ServiceManager;
@@ -592,6 +594,17 @@ public final class SystemServiceRegistry {
ctx.mMainThread.getHandler());
}});
registerService(Context.PERFORMANCE_HINT_SERVICE, PerformanceHintManager.class,
new CachedServiceFetcher<PerformanceHintManager>() {
@Override
public PerformanceHintManager createService(ContextImpl ctx)
throws ServiceNotFoundException {
IBinder hintBinder = ServiceManager.getServiceOrThrow(
Context.PERFORMANCE_HINT_SERVICE);
IHintManager hintService = IHintManager.Stub.asInterface(hintBinder);
return new PerformanceHintManager(hintService);
}});
registerService(Context.RECOVERY_SERVICE, RecoverySystem.class,
new CachedServiceFetcher<RecoverySystem>() {
@Override

View File

@@ -5224,6 +5224,14 @@ public abstract class Context {
*/
public static final String THERMAL_SERVICE = "thermalservice";
/**
* Use with {@link #getSystemService(String)} to retrieve a
* {@link android.os.PerformanceHintManager} for accessing the performance hinting service.
*
* @see #getSystemService(String)
*/
public static final String PERFORMANCE_HINT_SERVICE = "performance_hint";
/**
* Use with {@link #getSystemService(String)} to retrieve a
* {@link android.content.pm.ShortcutManager} for accessing the launcher shortcut service.

View File

@@ -0,0 +1,33 @@
/*
*
* Copyright 2021, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.os.IHintSession;
/** {@hide} */
interface IHintManager {
/**
* Creates a {@link Session} for the given set of threads and associates to a binder token.
*/
IHintSession createHintSession(in IBinder token, in int[] tids, long durationNanos);
/**
* Get preferred rate limit in nano second.
*/
long getHintSessionPreferredRate();
}

View File

@@ -0,0 +1,25 @@
/*
*
* Copyright 2021, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
/** {@hide} */
oneway interface IHintSession {
void updateTargetWorkDuration(long targetDurationNanos);
void reportActualWorkDuration(in long[] actualDurationNanos, in long[] timeStampNanos);
void close();
}

View File

@@ -0,0 +1,226 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
import com.android.internal.util.Preconditions;
import java.io.Closeable;
import java.util.ArrayList;
/** The PerformanceHintManager allows apps to send performance hint to system. */
@SystemService(Context.PERFORMANCE_HINT_SERVICE)
public final class PerformanceHintManager {
private static final String TAG = "PerformanceHintManager";
private final IHintManager mService;
// HAL preferred update rate
private final long mPreferredRate;
/** @hide */
public PerformanceHintManager(IHintManager service) {
mService = service;
try {
mPreferredRate = mService.getHintSessionPreferredRate();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Creates a {@link Session} for the given set of threads and sets their initial target work
* duration.
*
* @param tids The list of threads to be associated with this session. They must be part of
* this process' thread group.
* @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new
* session.
* @return the new session if it is supported on this device, null if hint session is not
* supported on this device.
*/
@Nullable
public Session createHintSession(@NonNull int[] tids, long initialTargetWorkDurationNanos) {
try {
IBinder token = new Binder();
IHintSession session = mService.createHintSession(token, tids,
initialTargetWorkDurationNanos);
if (session == null) return null;
return new Session(session, sNanoClock, mPreferredRate,
initialTargetWorkDurationNanos);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Get preferred update rate information for this device.
*
* @return the preferred update rate supported by device software.
*/
public long getPreferredUpdateRateNanos() {
return mPreferredRate;
}
/**
* A Session represents a group of threads with an inter-related workload such that hints for
* their performance should be considered as a unit. The threads in a given session should be
* long-life and not created or destroyed dynamically.
*
* <p>Each session is expected to have a periodic workload with a target duration for each
* cycle. The cycle duration is likely greater than the target work duration to allow other
* parts of the pipeline to run within the available budget. For example, a renderer thread may
* work at 60hz in order to produce frames at the display's frame but have a target work
* duration of only 6ms.</p>
*
* <p>Any call in this class will change its internal data, so you must do your own thread
* safety to protect from racing.</p>
*
* <p>Note that the target work duration can be {@link #updateTargetWorkDuration(long) updated}
* if workloads change.</p>
*
* <p>After each cycle of work, the client is expected to
* {@link #reportActualWorkDuration(long) report} the actual time taken to complete.</p>
*
* <p>All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.</p>
*/
public static class Session implements Closeable {
private final IHintSession mSession;
private final NanoClock mElapsedRealtimeClock;
// Target duration for choosing update rate
private long mTargetDurationInNanos;
// HAL preferred update rate
private long mPreferredRate;
// Last update timestamp
private long mLastUpdateTimeStamp = -1L;
// Cached samples
private final ArrayList<Long> mActualDurationNanos;
private final ArrayList<Long> mTimeStampNanos;
/** @hide */
public Session(IHintSession session, NanoClock elapsedRealtimeClock, long preferredRate,
long durationNanos) {
mSession = session;
mElapsedRealtimeClock = elapsedRealtimeClock;
mTargetDurationInNanos = durationNanos;
mPreferredRate = preferredRate;
mActualDurationNanos = new ArrayList<Long>();
mTimeStampNanos = new ArrayList<Long>();
mLastUpdateTimeStamp = mElapsedRealtimeClock.nanos();
}
/**
* Updates this session's target duration for each cycle of work.
*
* @param targetDurationNanos the new desired duration in nanoseconds
*/
public void updateTargetWorkDuration(long targetDurationNanos) {
Preconditions.checkArgumentPositive(targetDurationNanos, "the hint target duration"
+ " should be positive.");
try {
mSession.updateTargetWorkDuration(targetDurationNanos);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mTargetDurationInNanos = targetDurationNanos;
/**
* Most of the workload is target_duration dependent, so now clear the cached samples
* as they are most likely obsolete.
*/
mActualDurationNanos.clear();
mTimeStampNanos.clear();
mLastUpdateTimeStamp = mElapsedRealtimeClock.nanos();
}
/**
* Reports the actual duration for the last cycle of work.
*
* <p>The system will attempt to adjust the core placement of the threads within the thread
* group and/or the frequency of the core on which they are run to bring the actual duration
* close to the target duration.</p>
*
* @param actualDurationNanos how long the thread group took to complete its last task in
* nanoseconds
*/
public void reportActualWorkDuration(long actualDurationNanos) {
Preconditions.checkArgumentPositive(actualDurationNanos, "the actual duration should"
+ " be positive.");
final long now = mElapsedRealtimeClock.nanos();
mActualDurationNanos.add(actualDurationNanos);
mTimeStampNanos.add(now);
/**
* Use current sample to determine the rate limit. We can pick a shorter rate limit
* if any sample underperformed, however, it could be the lower level system is slow
* to react. So here we explicitly choose the rate limit with the latest sample.
*/
long rateLimit =
actualDurationNanos > mTargetDurationInNanos ? mPreferredRate
: 10 * mPreferredRate;
if (now - mLastUpdateTimeStamp <= rateLimit) {
return;
}
Preconditions.checkState(mActualDurationNanos.size() == mTimeStampNanos.size());
final int size = mActualDurationNanos.size();
long[] actualDurationArray = new long[size];
long[] timeStampArray = new long[size];
for (int i = 0; i < size; i++) {
actualDurationArray[i] = mActualDurationNanos.get(i);
timeStampArray[i] = mTimeStampNanos.get(i);
}
try {
mSession.reportActualWorkDuration(actualDurationArray, timeStampArray);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mActualDurationNanos.clear();
mTimeStampNanos.clear();
mLastUpdateTimeStamp = now;
}
/**
* Ends the current hint session.
*
* <p>Once called, you should not call anything else on this object.</p>
*/
public void close() {
try {
mSession.close();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
/**
* The interface is to make the FakeClock for testing.
* @hide
*/
public interface NanoClock {
/** Gets the current nanosecond instant of the clock. */
long nanos();
}
private static final NanoClock sNanoClock = new NanoClock() {
public long nanos() {
return SystemClock.elapsedRealtimeNanos();
}
};
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNotNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.os.PerformanceHintManager.Session;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class PerformanceHintManagerTest {
private static final long RATE_1000 = 1000L;
private static final long TARGET_166 = 166L;
private static final long DEFAULT_TARGET_NS = 16666666L;
private PerformanceHintManager mPerformanceHintManager;
@Mock
private IHintSession mIHintSessionMock;
@Before
public void setUp() {
mPerformanceHintManager =
InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
PerformanceHintManager.class);
MockitoAnnotations.initMocks(this);
}
private Session createSession() {
return mPerformanceHintManager.createHintSession(
new int[]{Process.myPid()}, DEFAULT_TARGET_NS);
}
@Test
public void testCreateHintSession() {
Session a = createSession();
Session b = createSession();
if (a == null) {
assertNull(b);
} else {
assertNotEquals(a, b);
}
}
@Test
public void testGetPreferredUpdateRateNanos() {
if (createSession() != null) {
assertTrue(mPerformanceHintManager.getPreferredUpdateRateNanos() > 0);
} else {
assertEquals(-1, mPerformanceHintManager.getPreferredUpdateRateNanos());
}
}
@Test
public void testUpdateTargetWorkDuration() {
Session s = createSession();
assumeNotNull(s);
s.updateTargetWorkDuration(100);
}
@Test
public void testUpdateTargetWorkDurationWithNegativeDuration() {
Session s = createSession();
assumeNotNull(s);
assertThrows(IllegalArgumentException.class, () -> {
s.updateTargetWorkDuration(-1);
});
}
@Test
public void testReportActualWorkDuration() {
Session s = createSession();
assumeNotNull(s);
s.updateTargetWorkDuration(100);
s.reportActualWorkDuration(1);
s.reportActualWorkDuration(100);
s.reportActualWorkDuration(1000);
}
@Test
public void testReportActualWorkDurationWithIllegalArgument() {
Session s = createSession();
assumeNotNull(s);
s.updateTargetWorkDuration(100);
assertThrows(IllegalArgumentException.class, () -> {
s.reportActualWorkDuration(-1);
});
}
@Test
public void testRateLimitWithDurationFastEnough() throws Exception {
FakeClock fakeClock = new FakeClock();
Session s = new Session(mIHintSessionMock, fakeClock, RATE_1000, TARGET_166);
reset(mIHintSessionMock);
fakeClock.setNow(0);
s.updateTargetWorkDuration(TARGET_166);
s.reportActualWorkDuration(TARGET_166 - 1);
s.reportActualWorkDuration(TARGET_166);
// we should not see update as the rate should be 10X for over-perform case.
verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
fakeClock.incrementClock(10 * RATE_1000);
s.reportActualWorkDuration(TARGET_166);
verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
fakeClock.incrementClock(1);
s.reportActualWorkDuration(TARGET_166);
// we should see update after rate limit
verify(mIHintSessionMock, times(1)).reportActualWorkDuration(
eq(new long[] {TARGET_166 - 1, TARGET_166, TARGET_166, TARGET_166}),
eq(new long[] {0, 0, 10 * RATE_1000, 10 * RATE_1000 + 1}));
reset(mIHintSessionMock);
s.reportActualWorkDuration(TARGET_166);
s.reportActualWorkDuration(TARGET_166 - 1);
s.reportActualWorkDuration(TARGET_166 - 2);
// we should not see update as the rate should be 10X for over-perform case.
verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
fakeClock.incrementClock(10 * RATE_1000 + 1);
s.reportActualWorkDuration(TARGET_166);
s.reportActualWorkDuration(TARGET_166 - 1);
// we should see update now
verify(mIHintSessionMock, times(1)).reportActualWorkDuration(
eq(new long[] {TARGET_166, TARGET_166 - 1, TARGET_166 - 2, TARGET_166}),
eq(new long[] {10 * RATE_1000 + 1, 10 * RATE_1000 + 1, 10 * RATE_1000 + 1,
(10 * RATE_1000 + 1) * 2}));
}
@Test
public void testRateLimitWithDurationTooSlow() throws Exception {
FakeClock fakeClock = new FakeClock();
Session s = new Session(mIHintSessionMock, fakeClock, RATE_1000, TARGET_166);
reset(mIHintSessionMock);
fakeClock.setNow(0);
s.updateTargetWorkDuration(TARGET_166);
verify(mIHintSessionMock, times(1)).updateTargetWorkDuration(eq(TARGET_166));
// shouldn't update before rate limit
s.reportActualWorkDuration(TARGET_166 + 1);
verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
// shouldn't update when the time is exactly at rate limit
fakeClock.incrementClock(RATE_1000);
s.reportActualWorkDuration(TARGET_166 + 1);
verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
// should be ready for sending hint
fakeClock.incrementClock(1);
s.reportActualWorkDuration(TARGET_166 + 1);
verify(mIHintSessionMock, times(1)).reportActualWorkDuration(
eq(new long[] {TARGET_166 + 1, TARGET_166 + 1, TARGET_166 + 1}),
eq(new long[] {0 , RATE_1000, RATE_1000 + 1}));
}
@Test
public void testCloseHintSession() {
Session s = createSession();
assumeNotNull(s);
s.close();
}
private static class FakeClock implements PerformanceHintManager.NanoClock {
private long mCurrentTime = 0L;
@Override
public long nanos() {
return mCurrentTime;
}
public void setNow(long nanos) {
mCurrentTime = nanos;
}
public void incrementClock(long nanos) {
mCurrentTime += nanos;
}
}
}