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