MediaUtils: Add ScopedStatistics for effect tuning

Used for performance tuning of Spatial audio.

Test: adb shell dumpsys media.audio_flinger
Test: atest mediautils_scopedstatistics_tests
Bug: 228862341
Change-Id: I914a4cf9815d49fa413c19cc562a3e1101a4ced2
diff --git a/media/utils/include/mediautils/ScopedStatistics.h b/media/utils/include/mediautils/ScopedStatistics.h
new file mode 100644
index 0000000..c5fc1e9
--- /dev/null
+++ b/media/utils/include/mediautils/ScopedStatistics.h
@@ -0,0 +1,81 @@
+/*
+ * 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 "MethodStatistics.h"
+#include <chrono>
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace android::mediautils {
+
+class ScopedStatistics {
+  public:
+    /**
+     * ScopedStatistics is a RAII way of obtaining
+     * execution time statistics for a scoped C++ block.
+     *
+     * It updates the MethodStatistics shared pointer parameter
+     * with the methodName parameter and the duration/lifetime of the
+     * ScopedStatistics object.
+     *
+     * Not thread-safe, but expected to run in a single execution
+     * thread, and there are no user serviceable parts exposed.
+     *
+     * Example:
+     *
+     * std::shared_ptr<mediautils::MethodStatistics<std::string>> stats =
+     *     std::make_shared<mediautils::MethodStatistics<std::string>>();
+     *
+     * // ...
+     * {
+     *    mediautils::ScopedStatistics scopedStatistics("MyClass:myMethod", stats);
+     *
+     *    // some work to be timed here - up to the end of the block.
+     * }
+     *
+     * \param methodName the methodname to use "ClassName::methodName"
+     * \param statistics a shared ptr to the MethodStatistics object to use.
+     */
+    ScopedStatistics(std::string methodName,
+               std::shared_ptr<mediautils::MethodStatistics<std::string>> statistics)
+        : mMethodName{std::move(methodName)}
+        , mStatistics{std::move(statistics)}
+        , mBegin{std::chrono::steady_clock::now()} {}
+
+    // No copy constructor.
+    ScopedStatistics(const ScopedStatistics& scopedStatistics) = delete;
+    ScopedStatistics& operator=(const ScopedStatistics& scopedStatistics) = delete;
+
+    ~ScopedStatistics() {
+        if (mStatistics) {
+            const float elapsedMs = std::chrono::duration_cast<std::chrono::nanoseconds>(
+                                            std::chrono::steady_clock::now() - mBegin)
+                                            .count() *
+                                    1e-6; // ns to ms.
+            mStatistics->event(mMethodName, elapsedMs);
+        }
+    }
+
+  private:
+    const std::string mMethodName;
+    const std::shared_ptr<mediautils::MethodStatistics<std::string>> mStatistics;
+    const std::chrono::steady_clock::time_point mBegin;
+};
+
+} // namespace android::mediautils
diff --git a/media/utils/tests/Android.bp b/media/utils/tests/Android.bp
index a6f408d..1024018 100644
--- a/media/utils/tests/Android.bp
+++ b/media/utils/tests/Android.bp
@@ -124,6 +124,27 @@
 }
 
 cc_test {
+    name: "mediautils_scopedstatistics_tests",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wextra",
+    ],
+
+    shared_libs: [
+        "libaudioutils",
+        "liblog",
+        "libmediautils",
+        "libutils",
+    ],
+
+    srcs: [
+        "mediautils_scopedstatistics_tests.cpp",
+    ],
+}
+
+cc_test {
     name: "methodstatistics_tests",
 
     cflags: [
diff --git a/media/utils/tests/mediautils_scopedstatistics_tests.cpp b/media/utils/tests/mediautils_scopedstatistics_tests.cpp
new file mode 100644
index 0000000..807ce63
--- /dev/null
+++ b/media/utils/tests/mediautils_scopedstatistics_tests.cpp
@@ -0,0 +1,54 @@
+/*
+ * 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 "mediautils_scopedstatistics_tests"
+
+#include <mediautils/ScopedStatistics.h>
+
+#include <atomic>
+#include <chrono>
+#include <gtest/gtest.h>
+#include <thread>
+#include <utils/Log.h>
+
+using namespace android::mediautils;
+using namespace std::chrono_literals;
+
+TEST(mediautils_scopedstatistics_tests, basic) {
+    auto methodStatistics = std::make_shared<MethodStatistics<std::string>>();
+    std::string METHOD_NAME{"MyMethod"};
+
+    // no stats before
+    auto empty = methodStatistics->getStatistics(METHOD_NAME);
+    ASSERT_EQ(0, empty.getN());
+
+    // create a scoped statistics object.
+    {
+        ScopedStatistics scopedStatistics(METHOD_NAME, methodStatistics);
+
+        std::this_thread::sleep_for(100ms);
+    }
+
+    // check that some stats were logged.
+    auto stats = methodStatistics->getStatistics(METHOD_NAME);
+    ASSERT_EQ(1, stats.getN());
+    auto mean = stats.getMean();
+
+    // mean should be about 100ms, but to avoid false failures,
+    // we check 50ms < mean < 300ms.
+    ASSERT_GT(mean, 50.);   // took more than 50ms.
+    ASSERT_LT(mean, 300.);  // took less than 300ms.
+}