Spatializer: track scheduler statistics

This requires the spatializer to be enabled to run.

Test: adb shell dumpsys media.audio_flinger
Test: atest media_process_tests
Bug: 228648325
Change-Id: I2315064a548d5dced82a08c7f64c7fc2e900905b
diff --git a/media/utils/Android.bp b/media/utils/Android.bp
index 3b079c6..d76602b 100644
--- a/media/utils/Android.bp
+++ b/media/utils/Android.bp
@@ -33,6 +33,7 @@
         "MediaUtilsDelayed.cpp",
         "MemoryLeakTrackUtil.cpp",
         "MethodStatistics.cpp",
+        "Process.cpp",
         "ProcessInfo.cpp",
         "SchedulingPolicyService.cpp",
         "ServiceUtilities.cpp",
diff --git a/media/utils/Process.cpp b/media/utils/Process.cpp
new file mode 100644
index 0000000..8fe8003
--- /dev/null
+++ b/media/utils/Process.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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 "Process"
+#include <utils/Log.h>
+#include <mediautils/Process.h>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <cstdlib>
+
+namespace {
+
+void processLine(std::string_view s, std::map<std::string, double>& m) {
+    if (s.empty()) return;
+
+    const size_t colon_pos = s.find(':');
+    if (colon_pos == std::string_view::npos) return;
+
+    const size_t space_pos = s.find(' ');
+    if (space_pos == 0 || space_pos == std::string_view::npos || space_pos > colon_pos) return;
+    std::string key(s.data(), s.data() + space_pos);
+
+    const size_t value_pos = s.find_first_not_of(' ', colon_pos + 1);
+    if (value_pos == std::string_view::npos) return;
+
+    const double value = strtod(s.data() + value_pos, nullptr /* end */);
+    m[std::move(key)] = value;
+}
+
+} // namespace
+
+namespace android::mediautils {
+
+std::string getThreadSchedAsString(pid_t tid) {
+    const pid_t pid = getpid();
+    const std::string path = std::string("/proc/").append(std::to_string(pid))
+            .append("/task/").append(std::to_string(tid)).append("/sched");
+    std::string sched;
+    (void)android::base::ReadFileToString(path.c_str(), &sched);
+    return sched;
+}
+
+std::map<std::string, double> parseThreadSchedString(const std::string& schedString) {
+    std::map<std::string, double> m;
+    if (schedString.empty()) return m;
+    std::vector<std::string> stringlist = android::base::Split(schedString, "\n");
+
+    //  OK we use values not strings... m["summary"] = stringlist[0];
+    for (size_t i = 2; i < stringlist.size(); ++i) {
+        processLine(stringlist[i], m);
+    }
+    return m;
+}
+
+} // namespace android::mediautils
diff --git a/media/utils/include/mediautils/Process.h b/media/utils/include/mediautils/Process.h
new file mode 100644
index 0000000..d249c3a
--- /dev/null
+++ b/media/utils/include/mediautils/Process.h
@@ -0,0 +1,122 @@
+/*
+ * 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 <string>
+#include <unistd.h>
+
+/*
+ * This header contains utilities to read the linux system /proc/pid files
+ *
+ * The format of this is not guaranteed to be stable, so use for diagnostic purposes only.
+ *
+ * The linux "proc" directory documentation:
+ * https://kernel.org/doc/Documentation/filesystems/proc.txt
+ * https://www.kernel.org/doc/html/latest/filesystems/proc.html?highlight=proc%20pid#chapter-3-per-process-parameters
+ */
+
+namespace android::mediautils {
+
+/**
+ * Return the thread schedule information for tid.
+ *
+ * String will be empty if the process does not have permission to
+ * access the /proc/pid tables, or if not on a Linux device.
+ *
+ * Linux scheduler documentation:
+ * https://www.kernel.org/doc/html/latest/scheduler/index.html
+ * https://man7.org/linux/man-pages/man7/sched.7.html
+ *
+ * Sample as follows:
+
+AudioOut_8D (10800, #threads: 36)
+-------------------------------------------------------------------
+se.exec_start                                :       8132077.598026
+se.vruntime                                  :        798689.872087
+se.sum_exec_runtime                          :        136466.957838
+se.nr_migrations                             :               132487
+se.statistics.sum_sleep_runtime              :       5629794.565945
+se.statistics.wait_start                     :             0.000000
+se.statistics.sleep_start                    :       8195727.586392
+se.statistics.block_start                    :             0.000000
+se.statistics.sleep_max                      :       1995665.869808
+se.statistics.block_max                      :             0.591675
+se.statistics.exec_max                       :             2.477580
+se.statistics.slice_max                      :             0.000000
+se.statistics.wait_max                       :             8.608642
+se.statistics.wait_sum                       :          4683.266835
+se.statistics.wait_count                     :               300964
+se.statistics.iowait_sum                     :             0.000000
+se.statistics.iowait_count                   :                    0
+se.statistics.nr_migrations_cold             :                    0
+se.statistics.nr_failed_migrations_affine    :                  297
+se.statistics.nr_failed_migrations_running   :                 1412
+se.statistics.nr_failed_migrations_hot       :                   96
+se.statistics.nr_forced_migrations           :                   26
+se.statistics.nr_wakeups                     :               281263
+se.statistics.nr_wakeups_sync                :                   84
+se.statistics.nr_wakeups_migrate             :               132322
+se.statistics.nr_wakeups_local               :                 2165
+se.statistics.nr_wakeups_remote              :               279098
+se.statistics.nr_wakeups_affine              :                    0
+se.statistics.nr_wakeups_affine_attempts     :                    0
+se.statistics.nr_wakeups_passive             :                    0
+se.statistics.nr_wakeups_idle                :                    0
+avg_atom                                     :             0.453434
+avg_per_cpu                                  :             1.030040
+nr_switches                                  :               300963
+nr_voluntary_switches                        :               281252
+nr_involuntary_switches                      :                19711
+se.load.weight                               :             73477120
+se.avg.load_sum                              :                   58
+se.avg.runnable_sum                          :                27648
+se.avg.util_sum                              :                21504
+se.avg.load_avg                              :                   48
+se.avg.runnable_avg                          :                    0
+se.avg.util_avg                              :                    0
+se.avg.last_update_time                      :        8132075824128
+se.avg.util_est.ewma                         :                    8
+se.avg.util_est.enqueued                     :                    1
+uclamp.min                                   :                    0
+uclamp.max                                   :                 1024
+effective uclamp.min                         :                    0
+effective uclamp.max                         :                 1024
+policy                                       :                    0
+prio                                         :                  101
+clock-delta                                  :                  163
+*/
+std::string getThreadSchedAsString(pid_t tid);
+
+/**
+ * Returns map for the raw thread schedule string.
+ */
+std::map<std::string, double> parseThreadSchedString(const std::string& schedString);
+
+/**
+ * Returns map for /proc/pid/task/tid/sched
+ */
+inline std::map<std::string, double> getThreadSchedAsMap(pid_t tid) {
+    return parseThreadSchedString(getThreadSchedAsString(tid));
+}
+
+// TODO: Extend to other /proc/pid file information.
+//
+// See "ps" command get_ps().
+// https://cs.android.com/android/platform/superproject/+/master:external/toybox/toys/posix/ps.c;l=707
+
+} // android::mediautils
diff --git a/media/utils/tests/Android.bp b/media/utils/tests/Android.bp
index 30c10b7..d9c2b21 100644
--- a/media/utils/tests/Android.bp
+++ b/media/utils/tests/Android.bp
@@ -64,6 +64,26 @@
 }
 
 cc_test {
+    name: "media_process_tests",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wextra",
+    ],
+
+    shared_libs: [
+        "liblog",
+        "libmediautils",
+        "libutils",
+    ],
+
+    srcs: [
+        "media_process_tests.cpp",
+    ],
+}
+
+cc_test {
     name: "media_synchronization_tests",
 
     cflags: [
diff --git a/media/utils/tests/media_process_tests.cpp b/media/utils/tests/media_process_tests.cpp
new file mode 100644
index 0000000..2ae3f70
--- /dev/null
+++ b/media/utils/tests/media_process_tests.cpp
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+#include <mediautils/Process.h>
+
+#define LOG_TAG "media_process_tests"
+
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+using namespace android;
+using namespace android::mediautils;
+
+TEST(media_process_tests, basic) {
+  const std::string schedString = getThreadSchedAsString(gettid());
+
+  (void)schedString;
+  // We don't test schedString, only that we haven't crashed.
+  // ASSERT_FALSE(schedString.empty());
+
+  // schedString is not normative.  So we conjure up our own string
+  const std::string fakeString = "\
+AudioOut_8D (10800, #threads: 36)\n\
+-------------------------------------------------------------------\n\
+se.exec_start                                :       8132077.598026\n\
+se.vruntime                                  :        798689.872087\n\
+se.sum_exec_runtime                          :        136466.957838\n\
+se.nr_migrations                             :               132487\n\
+se.statistics.sum_sleep_runtime              :       5629794.565945\n\
+se.statistics.wait_start                     :             0.000000\n\
+se.statistics.sleep_start                    :       8195727.586392\n\
+se.statistics.block_start                    :             0.000000\n\
+se.statistics.sleep_max                      :       1995665.869808\n\
+se.statistics.block_max                      :             0.591675\n\
+se.statistics.exec_max                       :             2.477580\n\
+se.statistics.slice_max                      :             0.000000\n\
+se.statistics.wait_max                       :             8.608642\n\
+se.statistics.wait_sum                       :          4683.266835\n\
+se.statistics.wait_count                     :               300964\n\
+se.statistics.iowait_sum                     :             0.000000\n\
+se.statistics.iowait_count                   :                    0\n\
+se.statistics.nr_migrations_cold             :                    0\n\
+se.statistics.nr_failed_migrations_affine    :                  297\n\
+se.statistics.nr_failed_migrations_running   :                 1412\n\
+se.statistics.nr_failed_migrations_hot       :                   96\n\
+se.statistics.nr_forced_migrations           :                   26\n\
+se.statistics.nr_wakeups                     :               281263\n\
+se.statistics.nr_wakeups_sync                :                   84\n\
+se.statistics.nr_wakeups_migrate             :               132322\n\
+se.statistics.nr_wakeups_local               :                 2165\n\
+se.statistics.nr_wakeups_remote              :               279098\n\
+se.statistics.nr_wakeups_affine              :                    0\n\
+se.statistics.nr_wakeups_affine_attempts     :                    0\n\
+se.statistics.nr_wakeups_passive             :                    0\n\
+se.statistics.nr_wakeups_idle                :                    0\n\
+avg_atom                                     :             0.453434\n\
+avg_per_cpu                                  :             1.030040\n\
+nr_switches                                  :               300963\n\
+nr_voluntary_switches                        :               281252\n\
+nr_involuntary_switches                      :                19711\n\
+se.load.weight                               :             73477120\n\
+se.avg.load_sum                              :                   58\n\
+se.avg.runnable_sum                          :                27648\n\
+se.avg.util_sum                              :                21504\n\
+se.avg.load_avg                              :                   48\n\
+se.avg.runnable_avg                          :                    0\n\
+se.avg.util_avg                              :                    0\n\
+se.avg.last_update_time                      :        8132075824128\n\
+se.avg.util_est.ewma                         :                    8\n\
+se.avg.util_est.enqueued                     :                    1\n\
+uclamp.min                                   :                    0\n\
+uclamp.max                                   :                 1024\n\
+effective uclamp.min                         :                    0\n\
+effective uclamp.max                         :                 1024\n\
+policy                                       :                    0\n\
+prio                                         :                  101\n\
+clock-delta                                  :                  163";
+
+  std::map<std::string, double> m = parseThreadSchedString(fakeString);
+
+  auto it = m.find("clock-delta");
+  ASSERT_NE(it, m.end());
+  ASSERT_EQ(it->second, 163);
+
+  it = m.find("se.avg.load_avg");
+  ASSERT_NE(it, m.end());
+  ASSERT_EQ(it->second, 48);
+}
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 0f27f90..f8c3ae4 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -65,6 +65,7 @@
 #include <media/nbaio/PipeReader.h>
 #include <media/nbaio/SourceAudioBufferProvider.h>
 #include <mediautils/BatteryNotifier.h>
+#include <mediautils/Process.h>
 
 #include <audiomanager/AudioManager.h>
 #include <powermanager/PowerManager.h>
@@ -923,6 +924,20 @@
 
     dprintf(fd, "  Local log:\n");
     mLocalLog.dump(fd, "   " /* prefix */, 40 /* lines */);
+
+    // --all does the statistics
+    bool dumpAll = false;
+    for (const auto &arg : args) {
+        if (arg == String16("--all")) {
+            dumpAll = true;
+        }
+    }
+    if (dumpAll || type() == SPATIALIZER) {
+        const std::string sched = mediautils::getThreadSchedAsString(getTid());
+        if (!sched.empty()) {
+            (void)write(fd, sched.c_str(), sched.size());
+        }
+    }
 }
 
 void AudioFlinger::ThreadBase::dumpBase_l(int fd, const Vector<String16>& args __unused)