Implement native PerformanceHint API
Test: atest PerformanceHintNativeTestCases
Bug: 194204196
Change-Id: Ie26e25e9ecf87046df92346dff54174934a8c73e
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 3ee2c18..32b7a07 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -57,6 +57,7 @@
"net.c",
"obb.cpp",
"permission_manager.cpp",
+ "performance_hint.cpp",
"sensor.cpp",
"sharedmem.cpp",
"storage_manager.cpp",
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index de6db1a..f33e118 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -312,6 +312,13 @@
LIBANDROID_PLATFORM {
global:
+ APerformanceHint_getManager;
+ APerformanceHint_createSession;
+ APerformanceHint_getPreferredUpdateRateNanos;
+ APerformanceHint_updateTargetWorkDuration;
+ APerformanceHint_reportActualWorkDuration;
+ APerformanceHint_closeSession;
+ APerformanceHint_setIHintManagerForTesting;
extern "C++" {
ASurfaceControl_registerSurfaceStatsListener*;
ASurfaceControl_unregisterSurfaceStatsListener*;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
new file mode 100644
index 0000000..95a2da9
--- /dev/null
+++ b/native/android/performance_hint.cpp
@@ -0,0 +1,241 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "perf_hint"
+
+#include <utility>
+#include <vector>
+
+#include <android/os/IHintManager.h>
+#include <android/os/IHintSession.h>
+#include <binder/Binder.h>
+#include <binder/IBinder.h>
+#include <binder/IServiceManager.h>
+#include <performance_hint_private.h>
+#include <utils/SystemClock.h>
+
+using namespace android;
+using namespace android::os;
+
+struct APerformanceHintSession;
+
+struct APerformanceHintManager {
+public:
+ static APerformanceHintManager* getInstance();
+ APerformanceHintManager(sp<IHintManager> service, int64_t preferredRateNanos);
+ APerformanceHintManager() = delete;
+ ~APerformanceHintManager() = default;
+
+ APerformanceHintSession* createSession(const int32_t* threadIds, size_t size,
+ int64_t initialTargetWorkDurationNanos);
+ int64_t getPreferredRateNanos() const;
+
+private:
+ static APerformanceHintManager* create(sp<IHintManager> iHintManager);
+
+ sp<IHintManager> mHintManager;
+ const int64_t mPreferredRateNanos;
+};
+
+struct APerformanceHintSession {
+public:
+ APerformanceHintSession(sp<IHintSession> session, int64_t preferredRateNanos,
+ int64_t targetDurationNanos);
+ APerformanceHintSession() = delete;
+ ~APerformanceHintSession();
+
+ int updateTargetWorkDuration(int64_t targetDurationNanos);
+ int reportActualWorkDuration(int64_t actualDurationNanos);
+
+private:
+ friend struct APerformanceHintManager;
+
+ sp<IHintSession> mHintSession;
+ // HAL preferred update rate
+ const int64_t mPreferredRateNanos;
+ // Target duration for choosing update rate
+ int64_t mTargetDurationNanos;
+ // Last update timestamp
+ int64_t mLastUpdateTimestamp;
+ // Cached samples
+ std::vector<int64_t> mActualDurationsNanos;
+ std::vector<int64_t> mTimestampsNanos;
+};
+
+static IHintManager* gIHintManagerForTesting = nullptr;
+static APerformanceHintManager* gHintManagerForTesting = nullptr;
+
+// ===================================== APerformanceHintManager implementation
+APerformanceHintManager::APerformanceHintManager(sp<IHintManager> manager,
+ int64_t preferredRateNanos)
+ : mHintManager(std::move(manager)), mPreferredRateNanos(preferredRateNanos) {}
+
+APerformanceHintManager* APerformanceHintManager::getInstance() {
+ if (gHintManagerForTesting) return gHintManagerForTesting;
+ if (gIHintManagerForTesting) {
+ APerformanceHintManager* manager = create(gIHintManagerForTesting);
+ gIHintManagerForTesting = nullptr;
+ return manager;
+ }
+ static APerformanceHintManager* instance = create(nullptr);
+ return instance;
+}
+
+APerformanceHintManager* APerformanceHintManager::create(sp<IHintManager> manager) {
+ if (!manager) {
+ manager = interface_cast<IHintManager>(
+ defaultServiceManager()->checkService(String16("performance_hint")));
+ }
+ if (manager == nullptr) {
+ ALOGE("%s: PerformanceHint service is not ready ", __FUNCTION__);
+ return nullptr;
+ }
+ int64_t preferredRateNanos = -1L;
+ binder::Status ret = manager->getHintSessionPreferredRate(&preferredRateNanos);
+ if (!ret.isOk()) {
+ ALOGE("%s: PerformanceHint cannot get preferred rate. %s", __FUNCTION__,
+ ret.exceptionMessage().c_str());
+ return nullptr;
+ }
+ if (preferredRateNanos <= 0) {
+ ALOGE("%s: PerformanceHint invalid preferred rate.", __FUNCTION__);
+ return nullptr;
+ }
+ return new APerformanceHintManager(std::move(manager), preferredRateNanos);
+}
+
+APerformanceHintSession* APerformanceHintManager::createSession(
+ const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos) {
+ sp<IBinder> token = sp<BBinder>::make();
+ std::vector<int32_t> tids(threadIds, threadIds + size);
+ sp<IHintSession> session;
+ binder::Status ret =
+ mHintManager->createHintSession(token, tids, initialTargetWorkDurationNanos, &session);
+ if (!ret.isOk() || !session) {
+ return nullptr;
+ }
+ return new APerformanceHintSession(std::move(session), mPreferredRateNanos,
+ initialTargetWorkDurationNanos);
+}
+
+int64_t APerformanceHintManager::getPreferredRateNanos() const {
+ return mPreferredRateNanos;
+}
+
+// ===================================== APerformanceHintSession implementation
+
+APerformanceHintSession::APerformanceHintSession(sp<IHintSession> session,
+ int64_t preferredRateNanos,
+ int64_t targetDurationNanos)
+ : mHintSession(std::move(session)),
+ mPreferredRateNanos(preferredRateNanos),
+ mTargetDurationNanos(targetDurationNanos),
+ mLastUpdateTimestamp(elapsedRealtimeNano()) {}
+
+APerformanceHintSession::~APerformanceHintSession() {
+ binder::Status ret = mHintSession->close();
+ if (!ret.isOk()) {
+ ALOGE("%s: HintSession close failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
+ }
+}
+
+int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNanos) {
+ if (targetDurationNanos <= 0) {
+ ALOGE("%s: targetDurationNanos must be positive", __FUNCTION__);
+ return EINVAL;
+ }
+ binder::Status ret = mHintSession->updateTargetWorkDuration(targetDurationNanos);
+ if (!ret.isOk()) {
+ ALOGE("%s: HintSessionn updateTargetWorkDuration failed: %s", __FUNCTION__,
+ ret.exceptionMessage().c_str());
+ return EPIPE;
+ }
+ mTargetDurationNanos = targetDurationNanos;
+ /**
+ * Most of the workload is target_duration dependent, so now clear the cached samples
+ * as they are most likely obsolete.
+ */
+ mActualDurationsNanos.clear();
+ mTimestampsNanos.clear();
+ mLastUpdateTimestamp = elapsedRealtimeNano();
+ return 0;
+}
+
+int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNanos) {
+ if (actualDurationNanos <= 0) {
+ ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
+ return EINVAL;
+ }
+ int64_t now = elapsedRealtimeNano();
+ mActualDurationsNanos.push_back(actualDurationNanos);
+ mTimestampsNanos.push_back(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.
+ */
+ int64_t rateLimit = actualDurationNanos > mTargetDurationNanos ? mPreferredRateNanos
+ : 10 * mPreferredRateNanos;
+ if (now - mLastUpdateTimestamp <= rateLimit) return 0;
+
+ binder::Status ret =
+ mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos);
+ mActualDurationsNanos.clear();
+ mTimestampsNanos.clear();
+ if (!ret.isOk()) {
+ ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
+ ret.exceptionMessage().c_str());
+ return EPIPE;
+ }
+ mLastUpdateTimestamp = now;
+ return 0;
+}
+
+// ===================================== C API
+APerformanceHintManager* APerformanceHint_getManager() {
+ return APerformanceHintManager::getInstance();
+}
+
+APerformanceHintSession* APerformanceHint_createSession(APerformanceHintManager* manager,
+ const int32_t* threadIds, size_t size,
+ int64_t initialTargetWorkDurationNanos) {
+ return manager->createSession(threadIds, size, initialTargetWorkDurationNanos);
+}
+
+int64_t APerformanceHint_getPreferredUpdateRateNanos(APerformanceHintManager* manager) {
+ return manager->getPreferredRateNanos();
+}
+
+int APerformanceHint_updateTargetWorkDuration(APerformanceHintSession* session,
+ int64_t targetDurationNanos) {
+ return session->updateTargetWorkDuration(targetDurationNanos);
+}
+
+int APerformanceHint_reportActualWorkDuration(APerformanceHintSession* session,
+ int64_t actualDurationNanos) {
+ return session->reportActualWorkDuration(actualDurationNanos);
+}
+
+void APerformanceHint_closeSession(APerformanceHintSession* session) {
+ delete session;
+}
+
+void APerformanceHint_setIHintManagerForTesting(void* iManager) {
+ delete gHintManagerForTesting;
+ gHintManagerForTesting = nullptr;
+ gIHintManagerForTesting = static_cast<IHintManager*>(iManager);
+}
diff --git a/native/android/tests/performance_hint/Android.bp b/native/android/tests/performance_hint/Android.bp
new file mode 100644
index 0000000..fdc1bc6
--- /dev/null
+++ b/native/android/tests/performance_hint/Android.bp
@@ -0,0 +1,65 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+cc_test {
+ name: "PerformanceHintNativeTestCases",
+
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+
+ srcs: ["PerformanceHintNativeTest.cpp"],
+
+ shared_libs: [
+ "libandroid",
+ "liblog",
+ "libbinder",
+ "libpowermanager",
+ "libutils",
+ ],
+
+ static_libs: [
+ "libbase",
+ "libgmock",
+ "libgtest",
+ ],
+ stl: "c++_shared",
+
+ test_suites: [
+ "device-tests",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ header_libs: [
+ "libandroid_headers_private",
+ ],
+}
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
new file mode 100644
index 0000000..284e9ee
--- /dev/null
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "PerformanceHintNativeTest"
+
+#include <android/os/IHintManager.h>
+#include <android/os/IHintSession.h>
+#include <binder/IBinder.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <performance_hint_private.h>
+#include <memory>
+#include <vector>
+
+using android::binder::Status;
+using android::os::IHintManager;
+using android::os::IHintSession;
+
+using namespace android;
+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),
+ (override));
+ MOCK_METHOD(Status, getHintSessionPreferredRate, (int64_t * _aidl_return), (override));
+ MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+};
+
+class MockIHintSession : public IHintSession {
+public:
+ MOCK_METHOD(Status, updateTargetWorkDuration, (int64_t targetDurationNanos), (override));
+ MOCK_METHOD(Status, reportActualWorkDuration,
+ (const ::std::vector<int64_t>& actualDurationNanos,
+ const ::std::vector<int64_t>& timeStampNanos),
+ (override));
+ MOCK_METHOD(Status, close, (), (override));
+ MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+};
+
+class PerformanceHintTest : public Test {
+public:
+ void SetUp() override {
+ mMockIHintManager = new StrictMock<MockIHintManager>();
+ APerformanceHint_setIHintManagerForTesting(mMockIHintManager);
+ }
+
+ void TearDown() override {
+ mMockIHintManager = nullptr;
+ // Destroys MockIHintManager.
+ APerformanceHint_setIHintManagerForTesting(nullptr);
+ }
+
+ APerformanceHintManager* createManager() {
+ EXPECT_CALL(*mMockIHintManager, getHintSessionPreferredRate(_))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(123L), Return(Status())));
+ return APerformanceHint_getManager();
+ }
+
+ StrictMock<MockIHintManager>* mMockIHintManager = nullptr;
+};
+
+TEST_F(PerformanceHintTest, TestGetPreferredUpdateRateNanos) {
+ APerformanceHintManager* manager = createManager();
+ int64_t preferredUpdateRateNanos = APerformanceHint_getPreferredUpdateRateNanos(manager);
+ EXPECT_EQ(123L, preferredUpdateRateNanos);
+}
+
+TEST_F(PerformanceHintTest, TestSession) {
+ 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);
+
+ int64_t targetDurationNanos = 10;
+ EXPECT_CALL(*iSession, updateTargetWorkDuration(Eq(targetDurationNanos))).Times(Exactly(1));
+ int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos);
+ EXPECT_EQ(0, result);
+
+ usleep(2); // Sleep for longer than preferredUpdateRateNanos.
+ int64_t actualDurationNanos = 20;
+ std::vector<int64_t> actualDurations;
+ actualDurations.push_back(20);
+ EXPECT_CALL(*iSession, reportActualWorkDuration(Eq(actualDurations), _)).Times(Exactly(1));
+ result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos);
+ EXPECT_EQ(0, result);
+
+ result = APerformanceHint_updateTargetWorkDuration(session, -1L);
+ EXPECT_EQ(EINVAL, result);
+ result = APerformanceHint_reportActualWorkDuration(session, -1L);
+ EXPECT_EQ(EINVAL, result);
+
+ EXPECT_CALL(*iSession, close()).Times(Exactly(1));
+ APerformanceHint_closeSession(session);
+}