periodically push AudioFlinger thread statistics to media metrics

Test: dumpsys media.metrics
Bug: 68148948
Change-Id: I9b1538a8cff3f89f4689dceda800132349338a0d
diff --git a/media/libnblog/Android.bp b/media/libnblog/Android.bp
index b15e79b..4a4aac1 100644
--- a/media/libnblog/Android.bp
+++ b/media/libnblog/Android.bp
@@ -13,6 +13,7 @@
         "libbinder",
         "libcutils",
         "liblog",
+        "libmediametrics",
         "libutils",
     ],
 
diff --git a/media/libnblog/NBLog.cpp b/media/libnblog/NBLog.cpp
index cd967e4..aee0ea3 100644
--- a/media/libnblog/NBLog.cpp
+++ b/media/libnblog/NBLog.cpp
@@ -788,7 +788,7 @@
 // writes the data to a map of class PerformanceAnalysis, based on their thread ID.
 void NBLog::MergeReader::processSnapshot(NBLog::Snapshot &snapshot, int author)
 {
-    PerformanceData& data = mThreadPerformanceData[author];
+    ReportPerformance::PerformanceData& data = mThreadPerformanceData[author];
     // We don't do "auto it" because it reduces readability in this case.
     for (EntryIterator it = snapshot.begin(); it != snapshot.end(); ++it) {
         switch (it->type) {
@@ -856,6 +856,19 @@
             processSnapshot(*(snapshots[i]), i);
         }
     }
+    checkPushToMediaMetrics();
+}
+
+void NBLog::MergeReader::checkPushToMediaMetrics()
+{
+    const nsecs_t now = systemTime();
+    for (auto& item : mThreadPerformanceData) {
+        ReportPerformance::PerformanceData& data = item.second;
+        if (now - data.start >= kPeriodicMediaMetricsPush) {
+            (void)ReportPerformance::sendToMediaMetrics(data);
+            data.reset();   // data is persistent per thread
+        }
+    }
 }
 
 void NBLog::MergeReader::dump(int fd, int indent)
@@ -864,8 +877,8 @@
     ReportPerformance::dump(fd, indent, mThreadPerformanceAnalysis);
     Json::Value root(Json::arrayValue);
     for (const auto& item : mThreadPerformanceData) {
-        const PerformanceData& data = item.second;
-        std::unique_ptr<Json::Value> threadData = dumpToJson(data);
+        const ReportPerformance::PerformanceData& data = item.second;
+        std::unique_ptr<Json::Value> threadData = ReportPerformance::dumpToJson(data);
         if (threadData == nullptr) {
             continue;
         }
diff --git a/media/libnblog/PerformanceAnalysis.cpp b/media/libnblog/PerformanceAnalysis.cpp
index 999b22a..ca9bc2c 100644
--- a/media/libnblog/PerformanceAnalysis.cpp
+++ b/media/libnblog/PerformanceAnalysis.cpp
@@ -48,6 +48,8 @@
 
 namespace android {
 
+namespace ReportPerformance {
+
 void Histogram::add(double value)
 {
     // TODO Handle domain and range error exceptions?
@@ -107,8 +109,6 @@
 
 //------------------------------------------------------------------------------
 
-namespace ReportPerformance {
-
 // Given an audio processing wakeup timestamp, buckets the time interval
 // since the previous timestamp into a histogram, searches for
 // outliers, analyzes the outlier series for unexpectedly
diff --git a/media/libnblog/ReportPerformance.cpp b/media/libnblog/ReportPerformance.cpp
index 98e11cd..aab67c6 100644
--- a/media/libnblog/ReportPerformance.cpp
+++ b/media/libnblog/ReportPerformance.cpp
@@ -29,6 +29,7 @@
 #include <sys/time.h>
 #include <utility>
 #include <json/json.h>
+#include <media/MediaAnalyticsItem.h>
 #include <media/nblog/NBLog.h>
 #include <media/nblog/PerformanceAnalysis.h>
 #include <media/nblog/ReportPerformance.h>
@@ -37,6 +38,8 @@
 
 namespace android {
 
+namespace ReportPerformance {
+
 std::unique_ptr<Json::Value> dumpToJson(const PerformanceData& data)
 {
     std::unique_ptr<Json::Value> rootPtr = std::make_unique<Json::Value>(Json::objectValue);
@@ -54,16 +57,76 @@
     return rootPtr;
 }
 
-//------------------------------------------------------------------------------
+bool sendToMediaMetrics(const PerformanceData& data)
+{
+    // See documentation for these metrics here:
+    // docs.google.com/document/d/11--6dyOXVOpacYQLZiaOY5QVtQjUyqNx2zT9cCzLKYE/edit?usp=sharing
+    static constexpr char kThreadType[] = "android.media.audiothread.type";
+    static constexpr char kThreadFrameCount[] = "android.media.audiothread.framecount";
+    static constexpr char kThreadSampleRate[] = "android.media.audiothread.samplerate";
+    static constexpr char kThreadWorkHist[] = "android.media.audiothread.workMs.hist";
+    static constexpr char kThreadLatencyHist[] = "android.media.audiothread.latencyMs.hist";
+    static constexpr char kThreadWarmupHist[] = "android.media.audiothread.warmupMs.hist";
+    static constexpr char kThreadUnderruns[] = "android.media.audiothread.underruns";
+    static constexpr char kThreadOverruns[] = "android.media.audiothread.overruns";
+    static constexpr char kThreadActive[] = "android.media.audiothread.activeMs";
+    static constexpr char kThreadDuration[] = "android.media.audiothread.durationMs";
 
-namespace ReportPerformance {
+    std::unique_ptr<MediaAnalyticsItem> item(new MediaAnalyticsItem("audiothread"));
+
+    const Histogram &workHist = data.workHist;
+    if (workHist.totalCount() > 0) {
+        item->setCString(kThreadWorkHist, workHist.toString().c_str());
+    }
+
+    const Histogram &latencyHist = data.latencyHist;
+    if (latencyHist.totalCount() > 0) {
+        item->setCString(kThreadLatencyHist, latencyHist.toString().c_str());
+    }
+
+    const Histogram &warmupHist = data.warmupHist;
+    if (warmupHist.totalCount() > 0) {
+        item->setCString(kThreadWarmupHist, warmupHist.toString().c_str());
+    }
+
+    if (data.underruns > 0) {
+        item->setInt64(kThreadUnderruns, data.underruns);
+    }
+
+    if (data.overruns > 0) {
+        item->setInt64(kThreadOverruns, data.overruns);
+    }
+
+    // Send to Media Metrics if the record is not empty.
+    // The thread and time info are added inside the if statement because
+    // we want to send them only if there are performance metrics to send.
+    if (item->count() > 0) {
+        // Add thread info fields.
+        const int type = data.type;
+        // TODO have a int-to-string mapping defined somewhere else for other thread types.
+        if (type == 2) {
+            item->setCString(kThreadType, "FASTMIXER");
+        } else {
+            item->setCString(kThreadType, "UNKNOWN");
+        }
+        item->setInt32(kThreadFrameCount, data.frameCount);
+        item->setInt32(kThreadSampleRate, data.sampleRate);
+        // Add time info fields.
+        item->setInt64(kThreadActive, data.active / 1000000);
+        item->setInt64(kThreadDuration, (systemTime() - data.start) / 1000000);
+        return item->selfrecord();
+    }
+    return false;
+}
+
+//------------------------------------------------------------------------------
 
 // TODO: use a function like this to extract logic from writeToFile
 // https://stackoverflow.com/a/9279620
 
 // Writes outlier intervals, timestamps, and histograms spanning long time intervals to file.
 // TODO: write data in binary format
-void writeToFile(const std::deque<std::pair<timestamp, Histogram>> &hists,
+void writeToFile(const std::deque<std::pair<timestamp, Hist>> &hists,
                  const std::deque<std::pair<msInterval, timestamp>> &outlierData,
                  const std::deque<timestamp> &peakTimestamps,
                  const char * directory, bool append, int author, log_hash_t hash) {
diff --git a/media/libnblog/include/media/nblog/NBLog.h b/media/libnblog/include/media/nblog/NBLog.h
index d6403d3..e43b026 100644
--- a/media/libnblog/include/media/nblog/NBLog.h
+++ b/media/libnblog/include/media/nblog/NBLog.h
@@ -440,6 +440,7 @@
         void    log(Event event, const void *data, size_t length);
 
         void    logvf(const char *fmt, va_list ap);
+
         // helper functions for logging parts of a formatted entry
         void    logStart(const char *fmt);
         void    logTimestampFormat();
@@ -499,10 +500,12 @@
     private:
         // Amount of tries for reader to catch up with writer in getSnapshot().
         static constexpr int kMaxObtainTries = 3;
+
         // invalidBeginTypes and invalidEndTypes are used to align the Snapshot::begin() and
         // Snapshot::end() EntryIterators to valid entries.
         static const std::unordered_set<Event> invalidBeginTypes;
         static const std::unordered_set<Event> invalidEndTypes;
+
         // declared as const because audio_utils_fifo() constructor
         sp<IMemory> mIMemory;       // ref-counted version, assigned only in constructor
 
@@ -561,6 +564,7 @@
         static void    appendFloat(String8 *body, const void *data);
         static void    appendPID(String8 *body, const void *data, size_t length);
         static void    appendTimestamp(String8 *body, const void *data);
+
         // The bufferDump functions are used for debugging only.
         static String8 bufferDump(const uint8_t *buffer, size_t size);
         static String8 bufferDump(const EntryIterator &it);
@@ -603,11 +607,17 @@
         MergeReader(const void *shared, size_t size, Merger &merger);
 
         void dump(int fd, int indent = 0);
+
         // process a particular snapshot of the reader
         void processSnapshot(Snapshot &snap, int author);
+
         // call getSnapshot of the content of the reader's buffer and process the data
         void getAndProcessSnapshot();
 
+        // check for periodic push of performance data to media metrics, and perform
+        // the send if it is time to do so.
+        void checkPushToMediaMetrics();
+
     private:
         // FIXME Needs to be protected by a lock,
         //       because even though our use of it is read-only there may be asynchronous updates
@@ -620,7 +630,11 @@
         ReportPerformance::PerformanceAnalysisMap mThreadPerformanceAnalysis;
 
         // compresses and stores audio performance data from each thread's buffers.
-        std::map<int /*author, i.e. thread index*/, PerformanceData> mThreadPerformanceData;
+        // first parameter is author, i.e. thread index.
+        std::map<int, ReportPerformance::PerformanceData> mThreadPerformanceData;
+
+        // how often to push data to Media Metrics
+        static constexpr nsecs_t kPeriodicMediaMetricsPush = s2ns((nsecs_t)2 * 60 * 60); // 2 hours
 
         // handle author entry by looking up the author's name and appending it to the body
         // returns number of bytes read from fmtEntry
diff --git a/media/libnblog/include/media/nblog/PerformanceAnalysis.h b/media/libnblog/include/media/nblog/PerformanceAnalysis.h
index 6677cde..80eddb1 100644
--- a/media/libnblog/include/media/nblog/PerformanceAnalysis.h
+++ b/media/libnblog/include/media/nblog/PerformanceAnalysis.h
@@ -29,6 +29,8 @@
 
 class String8;
 
+namespace ReportPerformance {
+
 // TODO make this a templated class and put it in a separate file.
 // The templated parameters would be bin size and low limit.
 /*
@@ -168,8 +170,6 @@
 
 //------------------------------------------------------------------------------
 
-namespace ReportPerformance {
-
 class PerformanceAnalysis;
 
 // a map of PerformanceAnalysis instances
@@ -223,7 +223,7 @@
     std::deque<timestamp> mPeakTimestamps;
 
     // stores buffer period histograms with timestamp of first sample
-    std::deque<std::pair<timestamp, Histogram>> mHists;
+    std::deque<std::pair<timestamp, Hist>> mHists;
 
     // Parameters used when detecting outliers
     struct BufferPeriod {
diff --git a/media/libnblog/include/media/nblog/ReportPerformance.h b/media/libnblog/include/media/nblog/ReportPerformance.h
index da0c7ca..09bb2a0 100644
--- a/media/libnblog/include/media/nblog/ReportPerformance.h
+++ b/media/libnblog/include/media/nblog/ReportPerformance.h
@@ -28,6 +28,8 @@
 
 namespace android {
 
+namespace ReportPerformance {
+
 struct PerformanceData;
 
 // Dumps performance data to a JSON format.
@@ -35,7 +37,12 @@
 // the header of an external library.
 std::unique_ptr<Json::Value> dumpToJson(const PerformanceData& data);
 
-namespace ReportPerformance {
+// Send one thread's data to media metrics, if the performance data is nontrivial (i.e. not
+// all zero values). Return true if data was sent, false if there is nothing to write
+// or an error occurred while writing.
+bool sendToMediaMetrics(const PerformanceData& data);
+
+//------------------------------------------------------------------------------
 
 constexpr int kMsPerSec = 1000;
 constexpr int kSecPerMin = 60;
@@ -43,7 +50,7 @@
 constexpr int kJiffyPerMs = 10; // time unit for histogram as a multiple of milliseconds
 
 // stores a histogram: key: observed buffer period (multiple of jiffy). value: count
-using Histogram = std::map<int, int>;
+using Hist = std::map<int, int>;
 
 using msInterval = double;
 using jiffyInterval = double;
@@ -66,7 +73,7 @@
 }
 
 // Writes outlier intervals, timestamps, peaks timestamps, and histograms to a file.
-void writeToFile(const std::deque<std::pair<timestamp, Histogram>> &hists,
+void writeToFile(const std::deque<std::pair<timestamp, Hist>> &hists,
                  const std::deque<std::pair<msInterval, timestamp>> &outlierData,
                  const std::deque<timestamp> &peakTimestamps,
                  const char * kDirectory, bool append, int author, log_hash_t hash);