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

Commit 80448085 authored by Sanna Catherine de Treville Wager's avatar Sanna Catherine de Treville Wager
Browse files

Write audio performance data to file

Created new file ReportPerformacne.cpp with file writing
functionality.  When short-term histogram array is full,
its data is aggregated into a long-term histogram array.
Cleaned up documentation and comments.

Test: dumpsys media.log

Change-Id: I19f48ea27ffc8205e67ea4ded04615d33a422966
parent c9bac6e4
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ cc_library_shared {
        "PerformanceAnalysis.cpp",
        "Pipe.cpp",
        "PipeReader.cpp",
        "ReportPerformance.cpp",
        "SourceAudioBufferProvider.cpp",
    ],

+8 −35
Original line number Diff line number Diff line
@@ -108,6 +108,7 @@
#include <audio_utils/roundup.h>
#include <media/nbaio/NBLog.h>
#include <media/nbaio/PerformanceAnalysis.h>
#include <media/nbaio/ReportPerformance.h>
// #include <utils/CallStack.h> // used to print callstack
#include <utils/Log.h>
#include <utils/String8.h>
@@ -869,42 +870,17 @@ std::unique_ptr<NBLog::Reader::Snapshot> NBLog::Reader::getSnapshot()

}

// writes sample deltas to file, either truncating or appending
inline void writeHistToFile(const std::vector<int64_t> &samples, bool append) {
    // name of file on audioserver
    static const char* const kName = (char *)"/data/misc/audioserver/sample_results.txt";
    // stores deltas between the samples
    std::vector<int64_t> intervals;
    for (size_t i = 1; i < samples.size(); ++i) {
        intervals.push_back(deltaMs(samples[i - 1], samples[i]));
    }
    if (intervals.empty()) return;
    // Deletes maximum value in a histogram. Temp quick fix.
    // FIXME: need to find root cause of approx. 35th element from the end
    // consistently being an outlier in the first histogram of a flush
    // ALOGW("%" PRId64 "before", (int64_t) *(std::max_element(intervals.begin(), intervals.end())));
    intervals.erase(std::max_element(intervals.begin(), intervals.end()));
    // ALOGW("%" PRId64 "after", (int64_t) *(std::max_element(intervals.begin(), intervals.end())));
    std::ofstream ofs;
    ofs.open(kName, append ? std::ios::app : std::ios::trunc);
    if (!ofs) {
        ALOGW("couldn't open file %s", kName);
        return;
    }
    for (size_t i = 0; i < intervals.size(); ++i) {
        ofs << intervals[i] << "\n";
    }
    ofs.close();
}

// TODO: move this to PerformanceAnalysis
// TODO: make call to dump periodic so that data in shared FIFO does not get overwritten
void NBLog::Reader::dump(int fd, size_t indent, NBLog::Reader::Snapshot &snapshot)
{
    //  CallStack cs(LOG_TAG);
    mFd = fd;
    mIndent = indent;
    String8 timestamp, body;
    // FIXME: this is not thread safe
    static PerformanceAnalysis performanceAnalysis; // used to store data and to call analysis functions
    // TODO: need a separate instance of performanceAnalysis for each thread
    // used to store data and to call analysis functions
    static ReportPerformance::PerformanceAnalysis performanceAnalysis;
    size_t lost = snapshot.lost() + (snapshot.begin() - EntryIterator(snapshot.data()));
    if (lost > 0) {
        body.appendFormat("warning: lost %zu bytes worth of events", lost);
@@ -926,15 +902,12 @@ void NBLog::Reader::dump(int fd, size_t indent, NBLog::Reader::Snapshot &snapsho
            memcpy(&hash, &(data->hash), sizeof(hash));
            int64_t ts;
            memcpy(&ts, &data->ts, sizeof(ts));
            performanceAnalysis.logTsEntry(data->author, ts);
            performanceAnalysis.logTsEntry(ts);
            ++entry;
            break;
        }
        case EVENT_AUDIO_STATE: {
            StateTsEntryWithAuthor *data = (StateTsEntryWithAuthor *) (entry->data);
            // TODO This memcpies are here to avoid unaligned memory access crash.
            // There's probably a more efficient way to do it
            performanceAnalysis.handleStateChange(data->author);
            performanceAnalysis.handleStateChange();
            ++entry;
            break;
        }
+62 −33
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@
#include <algorithm>
#include <climits>
#include <deque>
#include <fstream>
#include <iostream>
#include <math.h>
#include <numeric>
@@ -36,6 +35,7 @@
#include <audio_utils/roundup.h>
#include <media/nbaio/NBLog.h>
#include <media/nbaio/PerformanceAnalysis.h>
#include <media/nbaio/ReportPerformance.h>
// #include <utils/CallStack.h> // used to print callstack
#include <utils/Log.h>
#include <utils/String8.h>
@@ -45,6 +45,8 @@

namespace android {

namespace ReportPerformance {

PerformanceAnalysis::PerformanceAnalysis() {
    // These variables will be (FIXME) learned from the data
    kPeriodMs = 4; // typical buffer period (mode)
@@ -76,53 +78,87 @@ static int widthOf(int x) {
// outliers, analyzes the outlier series for unexpectedly
// small or large values and stores these as peaks, and flushes
// the timestamp series from memory.
void PerformanceAnalysis::processAndFlushTimeStampSeries(int author) {
void PerformanceAnalysis::processAndFlushTimeStampSeries() {
    // 1) analyze the series to store all outliers and their exact timestamps:
    storeOutlierData(mTimeStampSeries[author]);
    storeOutlierData(mTimeStampSeries);

    // 2) detect peaks in the outlier series
    detectPeaks();

    // 3) compute its histogram, append this to mRecentHists and erase the time series
    // FIXME: need to store the timestamp of the beginning of each histogram
    // FIXME: Restore LOG_HIST_FLUSH to separate histograms at every end-of-stream event
    // A histogram should not span data between audio off/on timespans
    mRecentHists.emplace_back(author, buildBuckets(mTimeStampSeries[author]));
    // 3) compute its histogram, append to mRecentHists and clear the time series
    mRecentHists.emplace_back(static_cast<timestamp>(mTimeStampSeries[0]),
                              buildBuckets(mTimeStampSeries));
    // do not let mRecentHists exceed capacity
    // ALOGD("mRecentHists size: %d", static_cast<int>(mRecentHists.size()));
    if (mRecentHists.size() >= kRecentHistsCapacity) {
        //  ALOGD("popped back mRecentHists");
        mRecentHists.pop_front();
    }
    mTimeStampSeries[author].clear();
    mTimeStampSeries.clear();
}

// forces short-term histogram storage to avoid adding idle audio time interval
// to buffer period data
void PerformanceAnalysis::handleStateChange(int author) {
void PerformanceAnalysis::handleStateChange() {
    ALOGD("handleStateChange");
    processAndFlushTimeStampSeries(author);
    processAndFlushTimeStampSeries();
    return;
}

// Takes a single buffer period timestamp entry with author information and stores it
// in a temporary series of timestamps. Once the series is full, the data is analyzed,
// Takes a single buffer period timestamp entry information and stores it in a
// temporary series of timestamps. Once the series is full, the data is analyzed,
// stored, and emptied.
// TODO: decide whether author or file location information is more important to store
// for now, only stores author (thread)
void PerformanceAnalysis::logTsEntry(int author, int64_t ts) {
void PerformanceAnalysis::logTsEntry(int64_t ts) {
    // TODO might want to filter excessively high outliers, which are usually caused
    // by the thread being inactive.
    // Store time series data for each reader in order to bucket it once there
    // is enough data. Then, write to recentHists as a histogram.
    mTimeStampSeries[author].push_back(ts);
    mTimeStampSeries.push_back(ts);
    // if length of the time series has reached kShortHistSize samples,
    // analyze the data and flush the timestamp series from memory
    if (mTimeStampSeries[author].size() >= kShortHistSize) {
        processAndFlushTimeStampSeries(author);
    if (mTimeStampSeries.size() >= kShortHistSize) {
        processAndFlushTimeStampSeries();
    }
}

// When the short-term histogram array mRecentHists has reached capacity,
// merge histograms for data compression and store them in mLongTermHists
// clears mRecentHists
// TODO: have logTsEntry write directly to mLongTermHists, discard mRecentHists,
// start a new histogram when a peak occurs
void PerformanceAnalysis::processAndFlushRecentHists() {

    // Buckets is used to aggregate short-term histograms.
    Histogram buckets;
    timestamp startingTs = mRecentHists[0].first;

    for (const auto &shortHist: mRecentHists) {
        // If the time between starting and ending timestamps has reached the maximum,
        // add the current histogram (buckets) to the long-term histogram buffer,
        // clear buckets, and start a new long-term histogram aggregation process.
        if (deltaMs(startingTs, shortHist.first) >= kMaxHistTimespanMs) {
            mLongTermHists.emplace_back(startingTs, std::move(buckets));
            buckets.clear();
            startingTs = shortHist.first;
            // When memory is full, delete oldest histogram
            // TODO use a circular buffer
            if (mLongTermHists.size() >= kLongTermHistsCapacity) {
                mLongTermHists.pop_front();
            }
        }

        // add current histogram to buckets
        for (const auto &countPair : shortHist.second) {
            buckets[countPair.first] += countPair.second;
        }
    }
    mRecentHists.clear();
    // TODO: decide when/where to call writeToFile
    // TODO: add a thread-specific extension to the file name
    static const char* const kName = (const char *) "/data/misc/audioserver/sample_results.txt";
    writeToFile(mOutlierData, mLongTermHists, kName, false);
}

// Given a series of outlier intervals (mOutlier data),
// looks for changes in distribution (peaks), which can be either positive or negative.
// The function sets the mean to the starting value and sigma to 0, and updates
@@ -131,7 +167,6 @@ void PerformanceAnalysis::logTsEntry(int author, int64_t ts) {
// are set to the peak value and 0.
void PerformanceAnalysis::detectPeaks() {
    if (mOutlierData.empty()) {
        ALOGD("peak detector called on empty array");
        return;
    }

@@ -166,14 +201,12 @@ void PerformanceAnalysis::detectPeaks() {
            mPeakDetectorSd = sqrt(std::accumulate(start, it + 1, 0.0,
                      [=](auto &a, auto &b){ return a + sqr(b.first - mPeakDetectorMean);})) /
                      ((kN > 1)? kN - 1 : kN); // kN - 1: mean is correlated with variance
            // ALOGD("value, mean, sd: %f, %f, %f", static_cast<double>(it->first), mean, sd);
        }
        // surprising value: store peak timestamp and reset mean, sd, and start iterator
        else {
            mPeakTimestamps.emplace_back(it->second);
            // TODO: remove pop_front once a circular buffer is in place
            if (mPeakTimestamps.size() >= kShortHistSize) {
                ALOGD("popped back mPeakTimestamps");
            if (mPeakTimestamps.size() >= kPeakSeriesSize) {
                mPeakTimestamps.pop_front();
            }
            mPeakDetectorMean = static_cast<double>(it->first);
@@ -181,9 +214,6 @@ void PerformanceAnalysis::detectPeaks() {
            start = it;
        }
    }
    //for (const auto &it : mPeakTimestamps) {
    //    ALOGE("mPeakTimestamps %f", static_cast<double>(it));
    //}
    return;
}

@@ -194,7 +224,6 @@ void PerformanceAnalysis::detectPeaks() {
// This function is applied to the time series before it is converted into a histogram.
void PerformanceAnalysis::storeOutlierData(const std::vector<int64_t> &timestamps) {
    if (timestamps.size() < 1) {
        ALOGE("storeOutlierData called on empty vector");
        return;
    }
    // first pass: need to initialize
@@ -207,10 +236,9 @@ void PerformanceAnalysis::storeOutlierData(const std::vector<int64_t> &timestamp
            mOutlierData.emplace_back(mElapsed, static_cast<uint64_t>(mPrevNs));
            // Remove oldest value if the vector is full
            // TODO: remove pop_front once circular buffer is in place
            // FIXME: change kShortHistSize to some other constant. Make sure it is large
            // enough that data will never be lost before being written to a long-term FIFO
            if (mOutlierData.size() >= kShortHistSize) {
                ALOGD("popped back mOutlierData");
            // FIXME: make sure kShortHistSize is large enough that that data will never be lost
            // before being written to file or to a FIFO
            if (mOutlierData.size() >= kOutlierSeriesSize) {
                mOutlierData.pop_front();
            }
            mElapsed = 0;
@@ -236,9 +264,8 @@ void PerformanceAnalysis::testFunction() {
}

// TODO Make it return a std::string instead of modifying body --> is this still relevant?
// FIXME: as can be seen when printing the values, the outlier timestamps typically occur
// in the first histogram 35 to 38 indices from the end (most often 35).
// TODO consider changing all ints to uint32_t or uint64_t
// TODO: move this to ReportPerformance, probably make it a friend function of PerformanceAnalysis
void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
    if (mRecentHists.size() < 1) {
        ALOGD("reportPerformance: mRecentHists is empty");
@@ -342,4 +369,6 @@ void PerformanceAnalysis::alertIfGlitch(const std::vector<int64_t> &samples) {
    return;
}

} // namespace ReportPerformance

}   // namespace android
+74 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 "ReportPerformance"

#include <fstream>
#include <iostream>
#include <queue>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/prctl.h>
#include <utility>
#include <media/nbaio/NBLog.h>
#include <media/nbaio/PerformanceAnalysis.h>
#include <media/nbaio/ReportPerformance.h>
// #include <utils/CallStack.h> // used to print callstack
#include <utils/Log.h>
#include <utils/String8.h>

namespace android {

namespace ReportPerformance {

// Writes outlier intervals, timestamps, and histograms spanning long time intervals to a file.
// TODO: format the data efficiently and write different types of data to different files
void writeToFile(std::deque<std::pair<outlierInterval, timestamp>> &outlierData,
                                    std::deque<std::pair<timestamp, Histogram>> &hists,
                                    const char * kName,
                                    bool append) {
    ALOGD("writing performance data to file");
    if (outlierData.empty() || hists.empty()) {
        return;
    }

    std::ofstream ofs;
    ofs.open(kName, append ? std::ios::app : std::ios::trunc);
    if (!ofs.is_open()) {
        ALOGW("couldn't open file %s", kName);
        return;
    }
    ofs << "Outlier data: interval and timestamp\n";
    for (const auto &outlier : outlierData) {
        ofs << outlier.first << ": " << outlier.second << "\n";
    }
    ofs << "Histogram data\n";
    for (const auto &hist : hists) {
        ofs << "\ttimestamp\n";
        ofs << hist.first << "\n";
        ofs << "\tbuckets and counts\n";
        for (const auto &bucket : hist.second) {
            ofs << bucket.first << ": " << bucket.second << "\n";
        }
    }
    ofs.close();
}

} // namespace ReportPerformance

}   // namespace android
+34 −51
Original line number Diff line number Diff line
@@ -23,10 +23,11 @@
#include <deque>
#include <vector>
#include "NBLog.h"
#include "ReportPerformance.h"

namespace android {

class String8;
namespace ReportPerformance {

class PerformanceAnalysis {
    // This class stores and analyzes audio processing wakeup timestamps from NBLog
@@ -37,42 +38,25 @@ public:

    PerformanceAnalysis();

    // FIXME: decide whether to use 64 or 32 bits
    typedef uint64_t log_hash_t;

    // stores a short-term histogram of size determined by kShortHistSize
    // key: observed buffer period. value: count
    // TODO: unsigned, unsigned
    // TODO: change this name to histogram
    using short_histogram = std::map<int, int>;

    using outlierInterval = uint64_t;
    // int64_t timestamps are converted to uint64_t in PerformanceAnalysis::storeOutlierData,
    // and all further analysis functions use uint64_t.
    using timestamp = uint64_t;
    using timestamp_raw = int64_t;

    // Given a series of audio processing wakeup timestamps,
    // compresses and and analyzes the data, and flushes
    // the timestamp series from memory.
    void processAndFlushTimeStampSeries(int author);
    void processAndFlushTimeStampSeries();

    // Called when an audio on/off event is read from the buffer
    // Called when an audio on/off event is read from the buffer,
    // e.g. EVENT_AUDIO_STATE.
    // calls flushTimeStampSeries on the data up to the event,
    // effectively skipping over the idle audio time interval
    // when writing buffer period data to memory.
    void handleStateChange(int author);
    // effectively discarding the idle audio time interval
    void handleStateChange();

    // When the short-term histogram array mRecentHists has reached capacity,
    // merges histograms for data compression and stores them in mLongTermHists
    void processAndFlushRecentHists();

    // Writes wakeup timestamp entry to log and runs analysis
    // author is the thread ID
    // TODO: check. if the thread has multiple histograms, is author info correct
    // FIXME: remove author from arglist. Want to call these function separately on
    // each thread’s data.
    // FIXME: decide whether to store the hash (source file location) instead
    // FIXME: If thread has multiple histograms, check that code works and correct
    // author is stored (test with multiple threads). Need to check that the current
    // code is not receiving data from multiple threads. This could cause odd values.
    void logTsEntry(int author, timestamp_raw ts);
    // TODO: make this thread safe. Each thread should have its own instance
    // of PerformanceAnalysis.
    void logTsEntry(timestamp_raw ts);

    // FIXME: make peakdetector and storeOutlierData a single function
    // Input: mOutlierData. Looks at time elapsed between outliers
@@ -97,8 +81,6 @@ public:
    // TODO incorporate this into the analysis (currently unused)
    void alertIfGlitch(const std::vector<timestamp_raw> &samples);

    ~PerformanceAnalysis() {}

private:

    // stores outlier analysis: <elapsed time between outliers in ms, outlier timestamp>
@@ -108,23 +90,24 @@ private:
    // a peak is a moment at which the average outlier interval changed significantly
    std::deque<timestamp> mPeakTimestamps;

    // TODO: turn these into circular buffers for better data flow
    // FIFO of small histograms
    // stores fixed-size short buffer period histograms with timestamp of first sample
    std::deque<std::pair<timestamp, Histogram>> mRecentHists;

    // FIFO of small histograms
    // stores fixed-size short buffer period histograms with hash and thread data
    // TODO: Turn it into a circular buffer for better data flow
    std::deque<std::pair<int, short_histogram>> mRecentHists;
    // stores fixed-size long-term buffer period histograms with timestamp of first sample
    std::deque<std::pair<timestamp, Histogram>> mLongTermHists;

    // map from author to vector of timestamps, collected from NBLog
    // when a vector reaches its maximum size, analysis is run and the data is deleted
    std::map<int, std::vector<timestamp_raw>> mTimeStampSeries;
    // vector of timestamps, collected from NBLog for a (TODO) specific thread
    // when a vector reaches its maximum size, the data is processed and flushed
    std::vector<timestamp_raw> mTimeStampSeries;

    // TODO: measure these from the data (e.g., mode) as they may change.
    // const int kGlitchThreshMs = 7;
    // const int kMsPerSec = 1000;
    static const int kMsPerSec = 1000;

    // Parameters used when detecting outliers
    // TODO: learn some of these from the data, delete unused ones
    // FIXME: decide whether to make kPeriodMs static.
    // The non-const values are (TODO: will be) learned from the data
    static const int kNumBuff = 3; // number of buffers considered in local history
    int kPeriodMs; // current period length is ideally 4 ms
    static const int kOutlierMs = 7; // values greater or equal to this cause glitches
@@ -135,8 +118,15 @@ private:
    // Peak detection: number of standard deviations from mean considered a significant change
    static const int kStddevThreshold = 5;

    // capacity allocated to data structures
    // TODO: adjust all of these values
    static const int kRecentHistsCapacity = 100; // number of short-term histograms stored in memory
    static const int kShortHistSize = 50; // number of samples in a short-term histogram
    static const int kOutlierSeriesSize = 100; // number of values stored in outlier array
    static const int kPeakSeriesSize = 100; // number of values stored in peak array
    static const int kLongTermHistsCapacity = 20; // number of long-term histogram stored in memory
    // maximum elapsed time between first and last timestamp of a long-term histogram
    static const int kMaxHistTimespanMs = 5 * kMsPerSec;

    // these variables are stored in-class to ensure continuity while analyzing the timestamp
    // series one short sequence at a time: the variables are not re-initialized every time.
@@ -149,14 +139,7 @@ private:

};

static inline int deltaMs(int64_t ns1, int64_t ns2) {
    return (ns2 - ns1) / (1000 * 1000);
}

static inline uint32_t log2(uint32_t x) {
    // This works for x > 0
    return 31 - __builtin_clz(x);
}
} // namespace ReportPerformance

}   // namespace android

Loading