Implement setThreads APIs for PerformanceHintManager.Session.

Previously the list of threads of a Session was only determined when the
Session was created. This means newly forked threads from existing
threads of the Session will not get the benefit and the clients have to
create new Session for that.

This patch adds a new method to allow clients to update the threads of
the Session.

Bug:b/244216750
Test: atest PerformanceHintManagerTest
Test: atest PerformanceHintNativeTest
Test: atest HintManagerServiceTest

Change-Id: Iae8cbb4ce86a44a7cd9d6e68673c48800bed3a4e
This commit is contained in:
Peiyong Lin
2022-11-11 18:28:12 +00:00
parent 13101fe9d2
commit 095de769a6
12 changed files with 321 additions and 10 deletions

View File

@@ -32484,6 +32484,7 @@ package android.os {
public static class PerformanceHintManager.Session implements java.io.Closeable {
method public void close();
method public void reportActualWorkDuration(long);
method public void setThreads(@NonNull int[]);
method public void updateTargetWorkDuration(long);
}

View File

@@ -1861,6 +1861,7 @@ package android.os {
}
public static class PerformanceHintManager.Session implements java.io.Closeable {
method @Nullable public int[] getThreadIds();
method public void sendHint(int);
field public static final int CPU_LOAD_DOWN = 1; // 0x1
field public static final int CPU_LOAD_RESET = 2; // 0x2

View File

@@ -24,10 +24,13 @@ 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);
IHintSession createHintSession(in IBinder token, in int[] tids, long durationNanos);
/**
* Get preferred rate limit in nano second.
*/
long getHintSessionPreferredRate();
long getHintSessionPreferredRate();
void setHintSessionThreads(in IHintSession hintSession, in int[] tids);
int[] getHintSessionThreadIds(in IHintSession hintSession);
}

View File

@@ -222,16 +222,50 @@ public final class PerformanceHintManager {
Reference.reachabilityFence(this);
}
}
/**
* Set a list of threads to the performance hint session. This operation will replace
* the current list of threads with the given list of threads.
* Note that this is not an oneway method.
*
* @param tids The list of threads to be associated with this session. They must be
* part of this app's thread group.
*
* @throws IllegalStateException if the hint session is not in the foreground.
* @throws IllegalArgumentException if the thread id list is empty.
* @throws SecurityException if any thread id doesn't belong to the application.
*/
public void setThreads(@NonNull int[] tids) {
if (mNativeSessionPtr == 0) {
return;
}
if (tids.length == 0) {
throw new IllegalArgumentException("Thread id list can't be empty.");
}
nativeSetThreads(mNativeSessionPtr, tids);
}
/**
* Returns the list of thread ids.
*
* @hide
*/
@TestApi
public @Nullable int[] getThreadIds() {
return nativeGetThreadIds(mNativeSessionPtr);
}
}
private static native long nativeAcquireManager();
private static native long nativeGetPreferredUpdateRateNanos(long nativeManagerPtr);
private static native long nativeCreateSession(long nativeManagerPtr,
int[] tids, long initialTargetWorkDurationNanos);
private static native int[] nativeGetThreadIds(long nativeSessionPtr);
private static native void nativeUpdateTargetWorkDuration(long nativeSessionPtr,
long targetDurationNanos);
private static native void nativeReportActualWorkDuration(long nativeSessionPtr,
long actualDurationNanos);
private static native void nativeCloseSession(long nativeSessionPtr);
private static native void nativeSendHint(long nativeSessionPtr, int hint);
private static native void nativeSetThreads(long nativeSessionPtr, int[] tids);
}

View File

@@ -41,6 +41,8 @@ typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
typedef void (*APH_closeSession)(APerformanceHintSession* session);
typedef void (*APH_sendHint)(APerformanceHintSession*, int32_t);
typedef void (*APH_setThreads)(APerformanceHintSession*, const int32_t*, size_t);
typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const);
bool gAPerformanceHintBindingInitialized = false;
APH_getManager gAPH_getManagerFn = nullptr;
@@ -50,6 +52,8 @@ APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
APH_closeSession gAPH_closeSessionFn = nullptr;
APH_sendHint gAPH_sendHintFn = nullptr;
APH_setThreads gAPH_setThreadsFn = nullptr;
APH_getThreadIds gAPH_getThreadIdsFn = nullptr;
void ensureAPerformanceHintBindingInitialized() {
if (gAPerformanceHintBindingInitialized) return;
@@ -95,6 +99,14 @@ void ensureAPerformanceHintBindingInitialized() {
"Failed to find required symbol "
"APerformanceHint_sendHint!");
gAPH_setThreadsFn = (APH_setThreads)dlsym(handle_, "APerformanceHint_setThreads");
LOG_ALWAYS_FATAL_IF(gAPH_setThreadsFn == nullptr,
"Failed to find required symbol APerformanceHint_setThreads!");
gAPH_getThreadIdsFn = (APH_getThreadIds)dlsym(handle_, "APerformanceHint_getThreadIds");
LOG_ALWAYS_FATAL_IF(gAPH_getThreadIdsFn == nullptr,
"Failed to find required symbol APerformanceHint_getThreadIds!");
gAPerformanceHintBindingInitialized = true;
}
@@ -150,6 +162,50 @@ static void nativeSendHint(JNIEnv* env, jclass clazz, jlong nativeSessionPtr, ji
gAPH_sendHintFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr), hint);
}
static void nativeSetThreads(JNIEnv* env, jclass clazz, jlong nativeSessionPtr, jintArray tids) {
ensureAPerformanceHintBindingInitialized();
if (tids == nullptr) {
return;
}
ScopedIntArrayRO tidsArray(env, tids);
std::vector<int32_t> tidsVector;
tidsVector.reserve(tidsArray.size());
for (size_t i = 0; i < tidsArray.size(); ++i) {
tidsVector.push_back(static_cast<int32_t>(tidsArray[i]));
}
gAPH_setThreadsFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
tidsVector.data(), tidsVector.size());
}
// This call should only be used for validation in tests only. This call will initiate two IPC
// calls, the first one is used to determined the size of the thread ids list, the second one
// is used to return the actual list.
static jintArray nativeGetThreadIds(JNIEnv* env, jclass clazz, jlong nativeSessionPtr) {
ensureAPerformanceHintBindingInitialized();
size_t size = 0;
gAPH_getThreadIdsFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr), nullptr,
&size);
if (size == 0) {
jintArray jintArr = env->NewIntArray(0);
return jintArr;
}
std::vector<int32_t> tidsVector(size);
gAPH_getThreadIdsFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
tidsVector.data(), &size);
jintArray jintArr = env->NewIntArray(size);
if (jintArr == nullptr) {
jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
return nullptr;
}
jint* threadIds = env->GetIntArrayElements(jintArr, 0);
for (int i = 0; i < size; ++i) {
threadIds[i] = tidsVector[i];
}
env->ReleaseIntArrayElements(jintArr, threadIds, 0);
return jintArr;
}
static const JNINativeMethod gPerformanceHintMethods[] = {
{"nativeAcquireManager", "()J", (void*)nativeAcquireManager},
{"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos},
@@ -158,6 +214,8 @@ static const JNINativeMethod gPerformanceHintMethods[] = {
{"nativeReportActualWorkDuration", "(JJ)V", (void*)nativeReportActualWorkDuration},
{"nativeCloseSession", "(J)V", (void*)nativeCloseSession},
{"nativeSendHint", "(JI)V", (void*)nativeSendHint},
{"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
{"nativeGetThreadIds", "(J)[I", (void*)nativeGetThreadIds},
};
int register_android_os_PerformanceHintManager(JNIEnv* env) {

View File

@@ -137,4 +137,13 @@ public class PerformanceHintManagerTest {
assumeNotNull(s);
s.close();
}
@Test
public void testSetThreadsWithIllegalArgument() {
Session session = createSession();
assumeNotNull(session);
assertThrows(IllegalArgumentException.class, () -> {
session.setThreads(new int[] { });
});
}
}

View File

@@ -330,6 +330,7 @@ LIBANDROID {
APerformanceHint_updateTargetWorkDuration; # introduced=Tiramisu
APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu
APerformanceHint_closeSession; # introduced=Tiramisu
APerformanceHint_setThreads; # introduced=UpsideDownCake
local:
*;
};
@@ -338,6 +339,7 @@ LIBANDROID_PLATFORM {
global:
APerformanceHint_setIHintManagerForTesting;
APerformanceHint_sendHint;
APerformanceHint_getThreadIds;
extern "C++" {
ASurfaceControl_registerSurfaceStatsListener*;
ASurfaceControl_unregisterSurfaceStatsListener*;

View File

@@ -62,18 +62,21 @@ private:
struct APerformanceHintSession {
public:
APerformanceHintSession(sp<IHintSession> session, int64_t preferredRateNanos,
int64_t targetDurationNanos);
APerformanceHintSession(sp<IHintManager> hintManager, sp<IHintSession> session,
int64_t preferredRateNanos, int64_t targetDurationNanos);
APerformanceHintSession() = delete;
~APerformanceHintSession();
int updateTargetWorkDuration(int64_t targetDurationNanos);
int reportActualWorkDuration(int64_t actualDurationNanos);
int sendHint(int32_t hint);
int setThreads(const int32_t* threadIds, size_t size);
int getThreadIds(int32_t* const threadIds, size_t* size);
private:
friend struct APerformanceHintManager;
sp<IHintManager> mHintManager;
sp<IHintSession> mHintSession;
// HAL preferred update rate
const int64_t mPreferredRateNanos;
@@ -140,7 +143,7 @@ APerformanceHintSession* APerformanceHintManager::createSession(
if (!ret.isOk() || !session) {
return nullptr;
}
return new APerformanceHintSession(std::move(session), mPreferredRateNanos,
return new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos,
initialTargetWorkDurationNanos);
}
@@ -150,10 +153,12 @@ int64_t APerformanceHintManager::getPreferredRateNanos() const {
// ===================================== APerformanceHintSession implementation
APerformanceHintSession::APerformanceHintSession(sp<IHintSession> session,
APerformanceHintSession::APerformanceHintSession(sp<IHintManager> hintManager,
sp<IHintSession> session,
int64_t preferredRateNanos,
int64_t targetDurationNanos)
: mHintSession(std::move(session)),
: mHintManager(hintManager),
mHintSession(std::move(session)),
mPreferredRateNanos(preferredRateNanos),
mTargetDurationNanos(targetDurationNanos),
mFirstTargetMetTimestamp(0),
@@ -260,6 +265,47 @@ int APerformanceHintSession::sendHint(int32_t hint) {
return 0;
}
int APerformanceHintSession::setThreads(const int32_t* threadIds, size_t size) {
if (size == 0) {
ALOGE("%s: the list of thread ids must not be empty.", __FUNCTION__);
return EINVAL;
}
std::vector<int32_t> tids(threadIds, threadIds + size);
binder::Status ret = mHintManager->setHintSessionThreads(mHintSession, tids);
if (!ret.isOk()) {
ALOGE("%s: failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
if (ret.exceptionCode() == binder::Status::Exception::EX_SECURITY ||
ret.exceptionCode() == binder::Status::Exception::EX_ILLEGAL_ARGUMENT) {
return EINVAL;
}
return EPIPE;
}
return 0;
}
int APerformanceHintSession::getThreadIds(int32_t* const threadIds, size_t* size) {
std::vector<int32_t> tids;
binder::Status ret = mHintManager->getHintSessionThreadIds(mHintSession, &tids);
if (!ret.isOk()) {
ALOGE("%s: failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
return EPIPE;
}
// When threadIds is nullptr, this is the first call to determine the size
// of the thread ids list.
if (threadIds == nullptr) {
*size = tids.size();
return 0;
}
// Second call to return the actual list of thread ids.
*size = tids.size();
for (size_t i = 0; i < *size; ++i) {
threadIds[i] = tids[i];
}
return 0;
}
// ===================================== C API
APerformanceHintManager* APerformanceHint_getManager() {
return APerformanceHintManager::getInstance();
@@ -293,6 +339,23 @@ int APerformanceHint_sendHint(void* session, int32_t hint) {
return reinterpret_cast<APerformanceHintSession*>(session)->sendHint(hint);
}
int APerformanceHint_setThreads(APerformanceHintSession* session, const int32_t* threadIds,
size_t size) {
if (session == nullptr) {
return EINVAL;
}
return session->setThreads(threadIds, size);
}
int APerformanceHint_getThreadIds(void* aPerformanceHintSession, int32_t* const threadIds,
size_t* const size) {
if (aPerformanceHintSession == nullptr) {
return EINVAL;
}
return static_cast<APerformanceHintSession*>(aPerformanceHintSession)
->getThreadIds(threadIds, size);
}
void APerformanceHint_setIHintManagerForTesting(void* iManager) {
delete gHintManagerForTesting;
gHintManagerForTesting = nullptr;

View File

@@ -37,10 +37,15 @@ using namespace testing;
class MockIHintManager : public IHintManager {
public:
MOCK_METHOD(Status, createHintSession,
(const ::android::sp<::android::IBinder>& token, const ::std::vector<int32_t>& tids,
int64_t durationNanos, ::android::sp<::android::os::IHintSession>* _aidl_return),
(const sp<IBinder>& token, const ::std::vector<int32_t>& tids,
int64_t durationNanos, ::android::sp<IHintSession>* _aidl_return),
(override));
MOCK_METHOD(Status, getHintSessionPreferredRate, (int64_t * _aidl_return), (override));
MOCK_METHOD(Status, setHintSessionThreads,
(const sp<IHintSession>& hintSession, const ::std::vector<int32_t>& tids),
(override));
MOCK_METHOD(Status, getHintSessionThreadIds,
(const sp<IHintSession>& hintSession, ::std::vector<int32_t>* tids), (override));
MOCK_METHOD(IBinder*, onAsBinder, (), (override));
};
@@ -141,3 +146,36 @@ TEST_F(PerformanceHintTest, TestSession) {
EXPECT_CALL(*iSession, close()).Times(Exactly(1));
APerformanceHint_closeSession(session);
}
TEST_F(PerformanceHintTest, SetThreads) {
APerformanceHintManager* manager = createManager();
std::vector<int32_t> tids;
tids.push_back(1);
tids.push_back(2);
int64_t targetDuration = 56789L;
StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>();
sp<IHintSession> session_sp(iSession);
EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _))
.Times(Exactly(1))
.WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status())));
APerformanceHintSession* session =
APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
ASSERT_TRUE(session);
std::vector<int32_t> emptyTids;
int result = APerformanceHint_setThreads(session, emptyTids.data(), emptyTids.size());
EXPECT_EQ(EINVAL, result);
std::vector<int32_t> newTids;
newTids.push_back(1);
newTids.push_back(3);
EXPECT_CALL(*mMockIHintManager, setHintSessionThreads(_, Eq(newTids)))
.Times(Exactly(1))
.WillOnce(Return(Status()));
result = APerformanceHint_setThreads(session, newTids.data(), newTids.size());
EXPECT_EQ(0, result);
}

View File

@@ -189,6 +189,8 @@ public final class HintManagerService extends SystemService {
private static native void nativeSendHint(long halPtr, int hint);
private static native void nativeSetThreads(long halPtr, int[] tids);
private static native long nativeGetHintSessionPreferredRate();
/** Wrapper for HintManager.nativeInit */
@@ -237,6 +239,11 @@ public final class HintManagerService extends SystemService {
public long halGetHintSessionPreferredRate() {
return nativeGetHintSessionPreferredRate();
}
/** Wrapper for HintManager.nativeSetThreads */
public void halSetThreads(long halPtr, int[] tids) {
nativeSetThreads(halPtr, tids);
}
}
@VisibleForTesting
@@ -399,6 +406,18 @@ public final class HintManagerService extends SystemService {
return mHintSessionPreferredRate;
}
@Override
public void setHintSessionThreads(@NonNull IHintSession hintSession, @NonNull int[] tids) {
AppHintSession appHintSession = (AppHintSession) hintSession;
appHintSession.setThreads(tids);
}
@Override
public int[] getHintSessionThreadIds(@NonNull IHintSession hintSession) {
AppHintSession appHintSession = (AppHintSession) hintSession;
return appHintSession.getThreadIds();
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
@@ -434,11 +453,12 @@ public final class HintManagerService extends SystemService {
final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient {
protected final int mUid;
protected final int mPid;
protected final int[] mThreadIds;
protected int[] mThreadIds;
protected final IBinder mToken;
protected long mHalSessionPtr;
protected long mTargetDurationNanos;
protected boolean mUpdateAllowed;
protected int[] mNewThreadIds;
protected AppHintSession(
int uid, int pid, int[] threadIds, IBinder token,
@@ -541,6 +561,38 @@ public final class HintManagerService extends SystemService {
}
}
public void setThreads(@NonNull int[] tids) {
synchronized (mLock) {
if (mHalSessionPtr == 0) {
return;
}
if (tids.length == 0) {
throw new IllegalArgumentException("Thread id list can't be empty.");
}
final int callingUid = Binder.getCallingUid();
final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
final long identity = Binder.clearCallingIdentity();
try {
if (!checkTidValid(callingUid, callingTgid, tids)) {
throw new SecurityException("Some tid doesn't belong to the application.");
}
} finally {
Binder.restoreCallingIdentity(identity);
}
if (!updateHintAllowed()) {
Slogf.v(TAG, "update hint not allowed, storing tids.");
mNewThreadIds = tids;
return;
}
mNativeWrapper.halSetThreads(mHalSessionPtr, tids);
mThreadIds = tids;
}
}
public int[] getThreadIds() {
return mThreadIds;
}
private void onProcStateChanged() {
updateHintAllowed();
}
@@ -556,6 +608,11 @@ public final class HintManagerService extends SystemService {
synchronized (mLock) {
if (mHalSessionPtr == 0) return;
mNativeWrapper.halResumeHintSession(mHalSessionPtr);
if (mNewThreadIds != null) {
mNativeWrapper.halSetThreads(mHalSessionPtr, mNewThreadIds);
mThreadIds = mNewThreadIds;
mNewThreadIds = null;
}
}
}

View File

@@ -87,6 +87,11 @@ static void sendHint(int64_t session_ptr, SessionHint hint) {
appSession->sendHint(hint);
}
static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadIds) {
sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
appSession->setThreads(threadIds);
}
static int64_t getHintSessionPreferredRate() {
int64_t rate = -1;
auto result = gPowerHalController.getHintSessionPreferredRate();
@@ -149,6 +154,16 @@ static void nativeSendHint(JNIEnv* env, jclass /* clazz */, jlong session_ptr, j
sendHint(session_ptr, static_cast<SessionHint>(hint));
}
static void nativeSetThreads(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jintArray tids) {
ScopedIntArrayRO arrayThreadIds(env, tids);
std::vector<int32_t> threadIds(arrayThreadIds.size());
for (size_t i = 0; i < arrayThreadIds.size(); i++) {
threadIds[i] = arrayThreadIds[i];
}
setThreads(session_ptr, threadIds);
}
static jlong nativeGetHintSessionPreferredRate(JNIEnv* /* env */, jclass /* clazz */) {
return static_cast<jlong>(getHintSessionPreferredRate());
}
@@ -164,6 +179,7 @@ static const JNINativeMethod sHintManagerServiceMethods[] = {
{"nativeUpdateTargetWorkDuration", "(JJ)V", (void*)nativeUpdateTargetWorkDuration},
{"nativeReportActualWorkDuration", "(J[J[J)V", (void*)nativeReportActualWorkDuration},
{"nativeSendHint", "(JI)V", (void*)nativeSendHint},
{"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
{"nativeGetHintSessionPreferredRate", "()J", (void*)nativeGetHintSessionPreferredRate},
};

View File

@@ -19,6 +19,7 @@ package com.android.server.power.hint;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -310,4 +311,32 @@ public class HintManagerServiceTest {
a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
assertTrue(a.updateHintAllowed());
}
@Test
public void testSetThreads() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
.createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
a.updateTargetWorkDuration(100L);
assertThrows(IllegalArgumentException.class, () -> {
a.setThreads(new int[]{});
});
a.setThreads(SESSION_TIDS_B);
verify(mNativeWrapperMock, times(1)).halSetThreads(anyLong(), eq(SESSION_TIDS_B));
assertArrayEquals(SESSION_TIDS_B, a.getThreadIds());
reset(mNativeWrapperMock);
// Set session to background, then the duration would not be updated.
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
FgThread.getHandler().runWithScissors(() -> { }, 500);
assertFalse(a.updateHintAllowed());
a.setThreads(SESSION_TIDS_A);
verify(mNativeWrapperMock, never()).halSetThreads(anyLong(), any());
}
}