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));
+}