Spatializer: Add rate based stats

A new class ThreadSnapshot encapsulates
current (and future) thread based statistics.

Test: adb shell dumpsys media.audio_flinger --all
Test: atest media_threadsnapshot_tests
Bug: 228648325
Change-Id: Idbfdee08032c56f2c47e2e48d2ceb0656168c125
diff --git a/media/utils/ThreadSnapshot.cpp b/media/utils/ThreadSnapshot.cpp
new file mode 100644
index 0000000..382738e
--- /dev/null
+++ b/media/utils/ThreadSnapshot.cpp
@@ -0,0 +1,145 @@
+/*
+ * 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 "ThreadSnapshot"
+#include <utils/Log.h>
+#include <utils/Timers.h>
+#include <mediautils/ThreadSnapshot.h>
+
+#include <mediautils/Process.h>
+
+namespace android::mediautils {
+
+pid_t ThreadSnapshot::getTid() const {
+    std::lock_guard lg(mLock);
+    return mState.mTid;
+}
+
+void ThreadSnapshot::setTid(pid_t tid) {
+    std::lock_guard lg(mLock);
+    if (mState.mTid == tid) return;
+    mState.reset(tid);
+}
+
+void ThreadSnapshot::reset() {
+    std::lock_guard lg(mLock);
+    mState.reset(mState.mTid);
+}
+
+void ThreadSnapshot::onBegin() {
+    std::string sched = getThreadSchedAsString(getTid()); // tid could race here,
+                                                          // accept as benign.
+    std::lock_guard lg(mLock);
+    mState.onBegin(std::move(sched));
+}
+
+void ThreadSnapshot::onEnd() {
+    std::lock_guard lg(mLock);
+    mState.onEnd();
+}
+
+std::string ThreadSnapshot::toString() const {
+    // Make a local copy of the stats data under lock.
+    State state;
+    {
+        std::lock_guard lg(mLock);
+        state = mState;
+    }
+    return state.toString();
+}
+
+void ThreadSnapshot::State::reset(pid_t tid) {
+    mTid = tid;
+    mBeginTimeNs = -2;
+    mEndTimeNs = -1;
+    mCumulativeTimeNs = 0;
+    mBeginSched.clear();
+}
+
+void ThreadSnapshot::State::onBegin(std::string sched) {
+    if (mBeginTimeNs < mEndTimeNs) {
+        mBeginTimeNs = systemTime();
+        mBeginSched = std::move(sched);
+    }
+}
+
+void ThreadSnapshot::State::onEnd() {
+    if (mEndTimeNs < mBeginTimeNs) {
+        mEndTimeNs = systemTime();
+        mCumulativeTimeNs += mEndTimeNs - mBeginTimeNs;
+    }
+}
+
+std::string ThreadSnapshot::State::toString() const {
+    if (mBeginTimeNs < 0) return {};  // never begun.
+
+    // compute time intervals.
+    const int64_t nowNs = systemTime();
+    int64_t cumulativeTimeNs = mCumulativeTimeNs;
+    int64_t diffNs = mEndTimeNs - mBeginTimeNs; // if onEnd() isn't matched, diffNs < 0.
+    if (diffNs < 0) {
+        diffNs = nowNs - mBeginTimeNs;
+        cumulativeTimeNs += diffNs;
+    }
+    // normalization for rate variables
+    const double lastRunPerSec =  1e9 / diffNs;
+    const double totalPerSec = 1e9 / cumulativeTimeNs;
+
+    // HANDLE THE SCHEDULER STATISTICS HERE
+    // current and differential statistics for the scheduler.
+    std::string schedNow = getThreadSchedAsString(mTid);
+    const auto schedMapThen = parseThreadSchedString(mBeginSched);
+    const auto schedMapNow = parseThreadSchedString(schedNow);
+    static const char * schedDiffKeyList[] = {
+        "se.sum_exec_runtime",
+        "se.nr_migrations",
+        "se.statistics.wait_sum",
+        "se.statistics.wait_count",
+        "se.statistics.iowait_sum",
+        "se.statistics.iowait_count",
+        "se.statistics.nr_forced_migrations",
+        "nr_involuntary_switches",
+    };
+
+    // compute differential rate statistics.
+    std::string diffString;
+    for (const auto diffKey : schedDiffKeyList) {
+        if (auto itThen = schedMapThen.find(diffKey);
+                itThen != schedMapThen.end()) {
+
+            if (auto itNow = schedMapNow.find(diffKey);
+                    itNow != schedMapNow.end()) {
+                auto diff = itNow->second - itThen->second;
+                diff *= lastRunPerSec;
+                auto total = itNow->second * totalPerSec;
+                diffString.append(diffKey).append("  last-run:")
+                        .append(std::to_string(diff))
+                        .append("  cumulative:")
+                        .append(std::to_string(total))
+                        .append("\n");
+            }
+        }
+    }
+
+    if (!diffString.empty()) {
+        schedNow.append("*** per second stats ***\n").append(diffString);
+    }
+
+    // Return snapshot string.
+    return schedNow;
+}
+
+} // android::mediautils