Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d6eee717 authored by Eric Tan's avatar Eric Tan
Browse files

periodically push AudioFlinger thread statistics to media metrics

Test: dumpsys media.metrics
Bug: 68148948
Change-Id: I9b1538a8cff3f89f4689dceda800132349338a0d
parent fefe3165
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@ cc_library_shared {
        "libbinder",
        "libcutils",
        "liblog",
        "libmediametrics",
        "libutils",
    ],

+16 −3
Original line number Diff line number Diff line
@@ -788,7 +788,7 @@ std::unique_ptr<NBLog::Snapshot> NBLog::Reader::getSnapshot()
// 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 @@ void NBLog::MergeReader::getAndProcessSnapshot()
            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 @@ void NBLog::MergeReader::dump(int fd, int indent)
    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;
        }
+2 −2
Original line number Diff line number Diff line
@@ -48,6 +48,8 @@

namespace android {

namespace ReportPerformance {

void Histogram::add(double value)
{
    // TODO Handle domain and range error exceptions?
@@ -107,8 +109,6 @@ std::string Histogram::toString() const {

//------------------------------------------------------------------------------

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
+66 −3
Original line number Diff line number Diff line
@@ -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 @@ std::unique_ptr<Json::Value> dumpToJson(const PerformanceData& data)
    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";

    std::unique_ptr<MediaAnalyticsItem> item(new MediaAnalyticsItem("audiothread"));

    const Histogram &workHist = data.workHist;
    if (workHist.totalCount() > 0) {
        item->setCString(kThreadWorkHist, workHist.toString().c_str());
    }

namespace ReportPerformance {
    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) {
+15 −1
Original line number Diff line number Diff line
@@ -440,6 +440,7 @@ public:
        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 @@ public:
    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 @@ public:
        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 @@ public:
        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 @@ public:
        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
Loading