* device from suspending. This method must be able to be called
* independently of enableAutosuspend().
*
+ * @param debugName debug string attached to the acquired IWakeLock. Wake
+ * lock names are not necessarily unique.
+ *
* @return lock the interface for the created wake lock.
*/
- acquireWakeLock() generates (IWakeLock lock);
+ acquireWakeLock(string debugName) generates (IWakeLock lock);
};
cpp_std: "c++17",
}
+cc_defaults {
+ name: "system_suspend_stats_defaults",
+ shared_libs: [
+ "libprotobuf-cpp-full",
+ ],
+ static_libs: ["SystemSuspendStatsProto"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ ],
+}
+
+cc_library {
+ name: "SystemSuspendStatsProto",
+ srcs: [
+ "SystemSuspendStats.proto",
+ ],
+ proto: {
+ export_proto_headers: true,
+ type: "full",
+ },
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ ],
+}
+
cc_binary {
name: "android.system.suspend@1.0-service",
relative_install_path: "hw",
defaults: [
"system_suspend_defaults",
+ "system_suspend_stats_defaults",
],
init_rc: ["android.system.suspend@1.0-service.rc"],
vintf_fragments: ["android.system.suspend@1.0-service.xml"],
shared_libs: ["android.system.suspend@1.0"],
+ static_libs: ["SystemSuspendStatsProto"],
srcs: [
"SystemSuspend.cpp",
"main.cpp",
// Do *NOT* use for compliance with *TS.
cc_test {
name: "SystemSuspendV1_0UnitTest",
- defaults: ["system_suspend_defaults"],
+ defaults: [
+ "system_suspend_defaults",
+ "system_suspend_stats_defaults",
+ ],
static_libs: ["android.system.suspend@1.0"],
srcs: [
"SystemSuspend.cpp",
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
+#include <google/protobuf/text_format.h>
#include <hidl/Status.h>
+#include <hwbinder/IPCThreadState.h>
#include <sys/stat.h>
#include <sys/types.h>
using ::android::base::ReadFdToString;
using ::android::base::WriteStringToFd;
+using ::android::hardware::IPCThreadState;
using ::android::hardware::Void;
using ::std::string;
return string{buf, static_cast<size_t>(n)};
}
+static inline int getCallingPid() {
+ return IPCThreadState::self()->getCallingPid();
+}
+
WakeLock::WakeLock(SystemSuspend* systemSuspend) : mSystemSuspend(systemSuspend) {
mSystemSuspend->incSuspendCounter();
}
WakeLock::~WakeLock() {
mSystemSuspend->decSuspendCounter();
+ mSystemSuspend->deleteWakeLockStatsEntry(reinterpret_cast<uint64_t>(this));
}
SystemSuspend::SystemSuspend(unique_fd wakeupCountFd, unique_fd stateFd)
mCounterCondVar(),
mSuspendCounter(0),
mWakeupCountFd(std::move(wakeupCountFd)),
- mStateFd(std::move(stateFd)) {}
+ mStateFd(std::move(stateFd)),
+ mStats() {}
Return<bool> SystemSuspend::enableAutosuspend() {
static bool initialized = false;
return true;
}
-Return<sp<IWakeLock>> SystemSuspend::acquireWakeLock() {
- return new WakeLock{this};
+Return<sp<IWakeLock>> SystemSuspend::acquireWakeLock(const hidl_string& name) {
+ IWakeLock* wl = new WakeLock{this};
+ {
+ auto l = std::lock_guard(mStatsLock);
+ WakeLockStats wlStats{};
+ wlStats.set_name(name);
+ wlStats.set_pid(getCallingPid());
+ // Use WakeLock's address as a unique identifier.
+ (*mStats.mutable_wake_lock_stats())[reinterpret_cast<uint64_t>(wl)] = wlStats;
+ }
+ return wl;
}
Return<void> SystemSuspend::debug(const hidl_handle& handle,
return Void();
}
int fd = handle->data[0];
- WriteStringToFd(std::to_string(mSuspendCounter) + "\n", fd);
+ string debugStr;
+ {
+ auto l = std::lock_guard(mStatsLock);
+ google::protobuf::TextFormat::PrintToString(mStats, &debugStr);
+ }
+ WriteStringToFd(debugStr, fd);
fsync(fd);
return Void();
}
}
}
+void SystemSuspend::deleteWakeLockStatsEntry(uint64_t id) {
+ auto l = std::lock_guard(mStatsLock);
+ mStats.mutable_wake_lock_stats()->erase(id);
+}
+
void SystemSuspend::initAutosuspend() {
std::thread autosuspendThread([this] {
while (true) {
#include <android-base/unique_fd.h>
#include <android/system/suspend/1.0/ISystemSuspend.h>
+#include <system/hardware/interfaces/suspend/1.0/default/SystemSuspendStats.pb.h>
#include <condition_variable>
#include <mutex>
public:
SystemSuspend(unique_fd wakeupCountFd, unique_fd stateFd);
Return<bool> enableAutosuspend() override;
- Return<sp<IWakeLock>> acquireWakeLock() override;
+ Return<sp<IWakeLock>> acquireWakeLock(const hidl_string& name) override;
Return<void> debug(const hidl_handle& handle, const hidl_vec<hidl_string>& options) override;
void incSuspendCounter();
void decSuspendCounter();
+ void deleteWakeLockStatsEntry(uint64_t id);
private:
void initAutosuspend();
uint32_t mSuspendCounter;
unique_fd mWakeupCountFd;
unique_fd mStateFd;
+
+ // mStats can be inconsistent with with mSuspendCounter since we use two separate locks to
+ // protect these. However, since mStats is only for debugging we prioritize performance.
+ // Never hold both locks at the same time to avoid deadlock.
+ std::mutex mStatsLock;
+ SystemSuspendStats mStats;
};
} // namespace V1_0
--- /dev/null
+// Copyright 2018 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.
+
+syntax = "proto3";
+
+// Collects information about individual WakeLock instances.
+message WakeLockStats {
+ string name = 1;
+ int32 pid = 2;
+}
+
+message SystemSuspendStats {
+ // Maps a unique id of a WakeLock instance to the corresponding WakeLockStats message.
+ map<uint64, WakeLockStats> wake_lock_stats = 1;
+}
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <cutils/native_handle.h>
+#include <google/protobuf/text_format.h>
#include <gtest/gtest.h>
#include <hidl/HidlTransportSupport.h>
bool isSystemSuspendBlocked() { return isReadBlocked(stateFd); }
+ sp<IWakeLock> acquireWakeLock() { return suspendService->acquireWakeLock("TestLock"); }
+
+ SystemSuspendStats getDebugDump() {
+ // Index 0 corresponds to the read end of the pipe; 1 to the write end.
+ int fds[2];
+ pipe2(fds, O_NONBLOCK);
+ native_handle_t* handle = native_handle_create(1, 0);
+ handle->data[0] = fds[1];
+
+ suspendService->debug(handle, {});
+ SystemSuspendStats stats{};
+ google::protobuf::TextFormat::ParseFromString(readFd(fds[0]), &stats);
+
+ native_handle_close(handle);
+ close(fds[0]);
+ close(fds[1]);
+
+ return stats;
+ }
+
+ size_t getWakeLockCount() { return getDebugDump().wake_lock_stats().size(); }
+
sp<ISystemSuspend> suspendService;
int stateFd;
int wakeupCountFd;
// Tests that upon WakeLock destruction SystemSuspend HAL is unblocked.
TEST_F(SystemSuspendTest, WakeLockDestructor) {
{
- sp<IWakeLock> wl = suspendService->acquireWakeLock();
+ sp<IWakeLock> wl = acquireWakeLock();
ASSERT_NE(wl, nullptr);
unblockSystemSuspendFromWakeupCount();
ASSERT_TRUE(isSystemSuspendBlocked());
// Tests that multiple WakeLocks correctly block SystemSuspend HAL.
TEST_F(SystemSuspendTest, MultipleWakeLocks) {
{
- sp<IWakeLock> wl1 = suspendService->acquireWakeLock();
+ sp<IWakeLock> wl1 = acquireWakeLock();
ASSERT_NE(wl1, nullptr);
ASSERT_TRUE(isSystemSuspendBlocked());
unblockSystemSuspendFromWakeupCount();
{
- sp<IWakeLock> wl2 = suspendService->acquireWakeLock();
+ sp<IWakeLock> wl2 = acquireWakeLock();
ASSERT_NE(wl2, nullptr);
ASSERT_TRUE(isSystemSuspendBlocked());
}
// Tests that upon thread deallocation WakeLock is destructed and SystemSuspend HAL is unblocked.
TEST_F(SystemSuspendTest, ThreadCleanup) {
std::thread clientThread([this] {
- sp<IWakeLock> wl = suspendService->acquireWakeLock();
+ sp<IWakeLock> wl = acquireWakeLock();
ASSERT_NE(wl, nullptr);
unblockSystemSuspendFromWakeupCount();
ASSERT_TRUE(isSystemSuspendBlocked());
TEST_F(SystemSuspendTest, CleanupOnAbort) {
ASSERT_EXIT(
{
- sp<IWakeLock> wl = suspendService->acquireWakeLock();
+ sp<IWakeLock> wl = acquireWakeLock();
ASSERT_NE(wl, nullptr);
std::abort();
},
ASSERT_FALSE(isSystemSuspendBlocked());
}
+// Test that debug dump has correct information about acquired WakeLocks.
+TEST_F(SystemSuspendTest, DebugDump) {
+ {
+ sp<IWakeLock> wl = suspendService->acquireWakeLock("TestLock");
+ SystemSuspendStats debugDump = getDebugDump();
+ ASSERT_EQ(debugDump.wake_lock_stats().size(), 1);
+ ASSERT_EQ(debugDump.wake_lock_stats().begin()->second.name(), "TestLock");
+ }
+ ASSERT_EQ(getWakeLockCount(), 0);
+}
+
+// Stress test acquiring/releasing WakeLocks.
+TEST_F(SystemSuspendTest, WakeLockStressTest) {
+ // numThreads threads will acquire/release numLocks locks each.
+ constexpr int numThreads = 10;
+ constexpr int numLocks = 10000;
+ std::thread tds[numThreads];
+
+ for (int i = 0; i < numThreads; i++) {
+ tds[i] = std::thread([this] {
+ for (int i = 0; i < numLocks; i++) {
+ sp<IWakeLock> wl = acquireWakeLock();
+ }
+ });
+ }
+ for (int i = 0; i < numThreads; i++) {
+ tds[i].join();
+ }
+ ASSERT_EQ(getWakeLockCount(), 0);
+}
+
} // namespace android
int main(int argc, char** argv) {