AudioFlinger, AudioPolicy: Add binder time check statistics
Binder method count and execution time now in dumpsys.
Add method name to TimeCheck abort logs.
Add MethodStatistics utility class to accumulate values.
Test: atest libmediautils_test
Test: atest method_statistics
Test: atest timecheck_tests
Test: adb shell dumpsys media.audio_flinger
Test: adb shell dumpsys media.audio_policy
Test: adb shell dumpsys media.metrics
Bug: 219958414
Change-Id: I7398d706e475596af6ad491463e87e92b09026d8
diff --git a/media/utils/Android.bp b/media/utils/Android.bp
index ba8f199..569ea2a 100644
--- a/media/utils/Android.bp
+++ b/media/utils/Android.bp
@@ -43,7 +43,7 @@
],
shared_libs: [
"libaudioclient_aidl_conversion",
- "libaudioutils", // for clock.h
+ "libaudioutils", // for clock.h, Statistics.h
"libbinder",
"libcutils",
"liblog",
diff --git a/media/utils/include/mediautils/MethodStatistics.h b/media/utils/include/mediautils/MethodStatistics.h
new file mode 100644
index 0000000..7d8061d
--- /dev/null
+++ b/media/utils/include/mediautils/MethodStatistics.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <map>
+#include <mutex>
+#include <string>
+
+#include <android-base/thread_annotations.h>
+#include <audio_utils/Statistics.h>
+
+namespace android::mediautils {
+
+/**
+ * MethodStatistics is used to associate Binder codes
+ * with a method name and execution time statistics.
+ *
+ * This is used to track binder transaction times for
+ * AudioFlinger and AudioPolicy services.
+ *
+ * Here, Code is the enumeration type for the method
+ * lookup.
+ */
+template <typename Code>
+class MethodStatistics {
+public:
+ using FloatType = float;
+ using StatsType = audio_utils::Statistics<FloatType>;
+
+ /**
+ * Method statistics.
+ *
+ * Initialized with the Binder transaction list for tracking AudioFlinger
+ * and AudioPolicyManager execution statistics.
+ */
+ explicit MethodStatistics(
+ const std::initializer_list<std::pair<const Code, std::string>>& methodMap = {})
+ : mMethodMap{methodMap} {}
+
+ /**
+ * Adds a method event, typically execution time in ms.
+ */
+ void event(Code code, FloatType executeMs) {
+ std::lock_guard lg(mLock);
+ mStatisticsMap[code].add(executeMs);
+ }
+
+ /**
+ * Returns the name for the method code.
+ */
+ std::string getMethodForCode(Code code) const {
+ auto it = mMethodMap.find(code);
+ return it == mMethodMap.end() ? std::to_string((int)code) : it->second;
+ }
+
+ /**
+ * Returns the number of times the method was invoked by event().
+ */
+ size_t getMethodCount(Code code) const {
+ std::lock_guard lg(mLock);
+ auto it = mStatisticsMap.find(code);
+ return it == mStatisticsMap.end() ? 0 : it->second.getN();
+ }
+
+ /**
+ * Returns the statistics object for the method.
+ */
+ StatsType getStatistics(Code code) const {
+ std::lock_guard lg(mLock);
+ auto it = mStatisticsMap.find(code);
+ return it == mStatisticsMap.end() ? StatsType{} : it->second;
+ }
+
+ /**
+ * Dumps the current method statistics.
+ */
+ std::string dump() const {
+ std::stringstream ss;
+ std::lock_guard lg(mLock);
+ for (const auto &[code, stats] : mStatisticsMap) {
+ ss << int(code) << " " << getMethodForCode(code) <<
+ " n=" << stats.getN() << " " << stats.toString() << "\n";
+ }
+ return ss.str();
+ }
+
+private:
+ const std::map<Code, std::string> mMethodMap;
+ mutable std::mutex mLock;
+ std::map<Code, StatsType> mStatisticsMap GUARDED_BY(mLock);
+};
+
+// Only if used, requires IBinder.h to be included at the location of invocation.
+#define METHOD_STATISTICS_BINDER_CODE_NAMES(CODE_TYPE) \
+ {(CODE_TYPE)IBinder::PING_TRANSACTION , "ping"}, \
+ {(CODE_TYPE)IBinder::DUMP_TRANSACTION , "dump"}, \
+ {(CODE_TYPE)IBinder::SHELL_COMMAND_TRANSACTION , "shellCommand"}, \
+ {(CODE_TYPE)IBinder::INTERFACE_TRANSACTION , "getInterfaceDescriptor"}, \
+ {(CODE_TYPE)IBinder::SYSPROPS_TRANSACTION , "SYSPROPS_TRANSACTION"}, \
+ {(CODE_TYPE)IBinder::EXTENSION_TRANSACTION , "EXTENSION_TRANSACTION"}, \
+ {(CODE_TYPE)IBinder::DEBUG_PID_TRANSACTION , "DEBUG_PID_TRANSACTION"}, \
+
+} // android::mediautils
diff --git a/media/utils/tests/Android.bp b/media/utils/tests/Android.bp
index f240dae..5498ac5 100644
--- a/media/utils/tests/Android.bp
+++ b/media/utils/tests/Android.bp
@@ -28,6 +28,27 @@
}
cc_test {
+ name: "methodstatistics_tests",
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+
+ shared_libs: [
+ "libaudioutils",
+ "liblog",
+ "libmediautils",
+ "libutils",
+ ],
+
+ srcs: [
+ "methodstatistics_tests.cpp",
+ ],
+}
+
+cc_test {
name: "timecheck_tests",
cflags: [
diff --git a/media/utils/tests/methodstatistics_tests.cpp b/media/utils/tests/methodstatistics_tests.cpp
new file mode 100644
index 0000000..85c4ad5
--- /dev/null
+++ b/media/utils/tests/methodstatistics_tests.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 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 "methodstatistics_tests"
+
+#include <mediautils/MethodStatistics.h>
+
+#include <atomic>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+using namespace android::mediautils;
+using CodeType = size_t;
+
+constexpr CodeType HELLO_CODE = 10;
+constexpr const char * HELLO_NAME = "hello";
+constexpr float HELLO_EVENTS[] = { 1.f, 3.f }; // needs lossless average
+
+constexpr CodeType WORLD_CODE = 21;
+constexpr const char * WORLD_NAME = "world";
+
+constexpr CodeType UNKNOWN_CODE = 12345;
+
+TEST(methodstatistics_tests, method_names) {
+ const MethodStatistics<CodeType> methodStatistics{
+ {HELLO_CODE, HELLO_NAME},
+ {WORLD_CODE, WORLD_NAME},
+ };
+
+ ASSERT_EQ(std::string(HELLO_NAME), methodStatistics.getMethodForCode(HELLO_CODE));
+ ASSERT_EQ(std::string(WORLD_NAME), methodStatistics.getMethodForCode(WORLD_CODE));
+ // an unknown code returns itself as a number.
+ ASSERT_EQ(std::to_string(UNKNOWN_CODE), methodStatistics.getMethodForCode(UNKNOWN_CODE));
+}
+
+TEST(methodstatistics_tests, events) {
+ MethodStatistics<CodeType> methodStatistics{
+ {HELLO_CODE, HELLO_NAME},
+ {WORLD_CODE, WORLD_NAME},
+ };
+
+ size_t n = 0;
+ float sum = 0.f;
+ for (const auto event : HELLO_EVENTS) {
+ methodStatistics.event(HELLO_CODE, event);
+ sum += event;
+ ++n;
+ }
+
+ const auto helloStats = methodStatistics.getStatistics(HELLO_CODE);
+ ASSERT_EQ((signed)n, helloStats.getN());
+ ASSERT_EQ(sum / n, helloStats.getMean());
+ ASSERT_EQ(n, methodStatistics.getMethodCount(HELLO_CODE));
+
+ const auto unsetStats = methodStatistics.getStatistics(UNKNOWN_CODE);
+ ASSERT_EQ(0, unsetStats.getN());
+ ASSERT_EQ(0.f, unsetStats.getMean());
+ ASSERT_EQ(0U, methodStatistics.getMethodCount(UNKNOWN_CODE));
+}