psh_utils: Internally cache power stats
To avoid frequent (expensive) collection of power stats
allow the use of cached values with a certain time
tolerance.
Flag: com.android.media.audioserver.power_stats
Test: atest powerstats_collector_tests
Test: atest audio_powerstatscollector_benchmark
Bug: 350114693
Change-Id: I952950e5373e3c4b8ebc765bf6fb2cf8def0962e
diff --git a/media/psh_utils/PowerStatsCollector.cpp b/media/psh_utils/PowerStatsCollector.cpp
index 7bee3f8..6e02993 100644
--- a/media/psh_utils/PowerStatsCollector.cpp
+++ b/media/psh_utils/PowerStatsCollector.cpp
@@ -32,12 +32,43 @@
return psc;
}
-std::shared_ptr<PowerStats> PowerStatsCollector::getStats() const {
+std::shared_ptr<const PowerStats> PowerStatsCollector::getStats(int64_t toleranceNs) {
+ // Check if there is a cached PowerStats result available.
+ // As toleranceNs may be different between callers, it may be that some callers
+ // are blocked on mMutexExclusiveFill for a new stats result, while other callers
+ // may find the current cached result acceptable (within toleranceNs).
+ if (toleranceNs > 0) {
+ auto result = checkLastStats(toleranceNs);
+ if (result) return result;
+ }
+
+ // Take the mMutexExclusiveFill to ensure only one thread is filling.
+ std::lock_guard lg1(mMutexExclusiveFill);
+ // As obtaining a new PowerStats snapshot might take some time,
+ // check again to see if another waiting thread filled the cached result for us.
+ if (toleranceNs > 0) {
+ auto result = checkLastStats(toleranceNs);
+ if (result) return result;
+ }
auto result = std::make_shared<PowerStats>();
(void)fill(result.get());
+ std::lock_guard lg2(mMutex);
+ mLastFetchNs = systemTime(SYSTEM_TIME_BOOTTIME);
+ mLastFetchStats = result;
return result;
}
+std::shared_ptr<const PowerStats> PowerStatsCollector::checkLastStats(int64_t toleranceNs) const {
+ if (toleranceNs > 0) {
+ // see if we can return an old result.
+ std::lock_guard lg(mMutex);
+ if (mLastFetchStats && systemTime(SYSTEM_TIME_BOOTTIME) - mLastFetchNs < toleranceNs) {
+ return mLastFetchStats;
+ }
+ }
+ return {};
+}
+
void PowerStatsCollector::addProvider(std::unique_ptr<PowerStatsProvider>&& powerStatsProvider) {
mPowerStatsProviders.emplace_back(std::move(powerStatsProvider));
}
diff --git a/media/psh_utils/benchmarks/Android.bp b/media/psh_utils/benchmarks/Android.bp
index 505ebba..20efaa9 100644
--- a/media/psh_utils/benchmarks/Android.bp
+++ b/media/psh_utils/benchmarks/Android.bp
@@ -28,3 +28,24 @@
"libutils",
],
}
+
+cc_benchmark {
+ name: "audio_powerstatscollector_benchmark",
+
+ srcs: ["audio_powerstatscollector_benchmark.cpp"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ static_libs: [
+ "libaudioutils",
+ "libpshutils",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "libcutils",
+ "liblog",
+ "libutils",
+ ],
+}
diff --git a/media/psh_utils/benchmarks/audio_powerstatscollector_benchmark.cpp b/media/psh_utils/benchmarks/audio_powerstatscollector_benchmark.cpp
new file mode 100644
index 0000000..9e581bc
--- /dev/null
+++ b/media/psh_utils/benchmarks/audio_powerstatscollector_benchmark.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 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 "audio_token_benchmark"
+#include <utils/Log.h>
+
+#include <psh_utils/PowerStatsCollector.h>
+
+#include <benchmark/benchmark.h>
+
+/*
+ Pixel 8 Pro
+------------------------------------------------------------------------------------------
+ Benchmark Time CPU Iteration
+------------------------------------------------------------------------------------------
+audio_powerstatscollector_benchmark:
+ #BM_StatsToleranceMs/0 1.2005660120994434E8 ns 2532739.72 ns 100
+ #BM_StatsToleranceMs/50 1281.095987079007 ns 346.0322183913503 ns 2022168
+ #BM_StatsToleranceMs/100 459.9668862534226 ns 189.47902626735942 ns 2891307
+ #BM_StatsToleranceMs/200 233.8438662484292 ns 149.84041813854736 ns 4407343
+ #BM_StatsToleranceMs/500 184.42197142314103 ns 144.86896036787098 ns 7295167
+*/
+
+// We check how expensive it is to query stats depending
+// on the tolerance to reuse the cached values.
+// A tolerance of 0 means we always fetch stats.
+static void BM_StatsToleranceMs(benchmark::State& state) {
+ auto& collector = android::media::psh_utils::PowerStatsCollector::getCollector();
+ const int64_t toleranceNs = state.range(0) * 1'000'000;
+ while (state.KeepRunning()) {
+ collector.getStats(toleranceNs);
+ benchmark::ClobberMemory();
+ }
+}
+
+// Here we test various time tolerances (given in milliseconds here)
+BENCHMARK(BM_StatsToleranceMs)->Arg(0)->Arg(50)->Arg(100)->Arg(200)->Arg(500);
+
+BENCHMARK_MAIN();
diff --git a/media/psh_utils/include/psh_utils/PerformanceFixture.h b/media/psh_utils/include/psh_utils/PerformanceFixture.h
index 2d8b38a..092a508 100644
--- a/media/psh_utils/include/psh_utils/PerformanceFixture.h
+++ b/media/psh_utils/include/psh_utils/PerformanceFixture.h
@@ -59,7 +59,7 @@
std::array<unsigned, 3> coreSelection{0U, mCores / 2 + 1, mCores - 1};
mCore = coreSelection[std::min((size_t)coreClass, std::size(coreSelection) - 1)];
- const auto& collector = android::media::psh_utils::PowerStatsCollector::getCollector();
+ auto& collector = android::media::psh_utils::PowerStatsCollector::getCollector();
mStartStats = collector.getStats();
const pid_t tid = gettid(); // us.
@@ -102,7 +102,7 @@
unsigned mCores = 0;
int mCore = 0;
CoreClass mCoreClass = CORE_LITTLE;
- std::shared_ptr<android::media::psh_utils::PowerStats> mStartStats;
+ std::shared_ptr<const android::media::psh_utils::PowerStats> mStartStats;
};
} // namespace android::media::psh_utils
diff --git a/media/psh_utils/include/psh_utils/PowerStatsCollector.h b/media/psh_utils/include/psh_utils/PowerStatsCollector.h
index 437b93d..e3f8ea8 100644
--- a/media/psh_utils/include/psh_utils/PowerStatsCollector.h
+++ b/media/psh_utils/include/psh_utils/PowerStatsCollector.h
@@ -17,6 +17,7 @@
#pragma once
#include "PowerStats.h"
+#include <android-base/thread_annotations.h>
#include <memory>
#include <utils/Errors.h> // status_t
@@ -34,17 +35,25 @@
// singleton getter
static PowerStatsCollector& getCollector();
- // get a snapshot of the state.
- std::shared_ptr<PowerStats> getStats() const;
+ // Returns a snapshot of the state.
+ // If toleranceNs > 0, we permit the use of a stale snapshot taken within that tolerance.
+ std::shared_ptr<const PowerStats> getStats(int64_t toleranceNs = 0)
+ EXCLUDES(mMutex, mMutexExclusiveFill);
private:
PowerStatsCollector(); // use the singleton getter
+ // Returns non-empty PowerStats if we have a previous stats snapshot within toleranceNs.
+ std::shared_ptr<const PowerStats> checkLastStats(int64_t toleranceNs) const EXCLUDES(mMutex);
int fill(PowerStats* stats) const;
void addProvider(std::unique_ptr<PowerStatsProvider>&& powerStatsProvider);
+ mutable std::mutex mMutexExclusiveFill;
+ mutable std::mutex mMutex;
// addProvider is called in the ctor, so effectively const.
std::vector<std::unique_ptr<PowerStatsProvider>> mPowerStatsProviders;
+ int64_t mLastFetchNs GUARDED_BY(mMutex) = 0;
+ std::shared_ptr<const PowerStats> mLastFetchStats GUARDED_BY(mMutex);
};
} // namespace android::media::psh_utils
diff --git a/media/psh_utils/tests/powerstats_collector_tests.cpp b/media/psh_utils/tests/powerstats_collector_tests.cpp
index 5115d09..35c264a 100644
--- a/media/psh_utils/tests/powerstats_collector_tests.cpp
+++ b/media/psh_utils/tests/powerstats_collector_tests.cpp
@@ -27,7 +27,7 @@
}
TEST(powerstat_collector_tests, basic) {
- const auto& psc = PowerStatsCollector::getCollector();
+ auto& psc = PowerStatsCollector::getCollector();
// This test is used for debugging the string through logcat, we validate a non-empty string.
auto powerStats = psc.getStats();