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

Commit ee67de17 authored by Sanna Catherine de Treville Wager's avatar Sanna Catherine de Treville Wager Committed by Android (Google) Code Review
Browse files

Merge changes I776e35e7,Ie696cd90

* changes:
  Organize PerformanceAnalysis members in structs
  Write peaks to file. Minor cleanup
parents 66d2ea48 716d2c66
Loading
Loading
Loading
Loading
+42 −50
Original line number Diff line number Diff line
@@ -87,12 +87,12 @@ void PerformanceAnalysis::processAndFlushTimeStampSeries() {

    // if the current histogram has spanned its maximum time interval,
    // insert a new empty histogram to the front of mHists
    if (deltaMs(mHists[0].first, mTimeStampSeries[0]) >= kMaxHistTimespanMs) {
    if (deltaMs(mHists[0].first, mTimeStampSeries[0]) >= kMaxLength.HistTimespanMs) {
        mHists.emplace_front(static_cast<uint64_t>(mTimeStampSeries[0]),
                             std::map<int, int>());
        // When memory is full, delete oldest histogram
        if (mHists.size() >= kHistsCapacity) {
            mHists.resize(kHistsCapacity);
        if (mHists.size() >= kMaxLength.Hists) {
            mHists.resize(kMaxLength.Hists);
        }
    }

@@ -125,7 +125,7 @@ void PerformanceAnalysis::logTsEntry(int64_t 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.size() >= kHistSize) {
    if (mTimeStampSeries.size() >= kMaxLength.TimeStamps) {
        processAndFlushTimeStampSeries();
    }
}
@@ -152,36 +152,39 @@ void PerformanceAnalysis::detectPeaks() {
    // the mean and standard deviation are updated every time a peak is detected
    // initialize first time. The mean from the previous sequence is stored
    // for the next sequence. Here, they are initialized for the first time.
    if (mPeakDetectorMean < 0) {
        mPeakDetectorMean = static_cast<double>(start->first);
        mPeakDetectorSd = 0;
    if (mOutlierDistribution.Mean < 0) {
        mOutlierDistribution.Mean = static_cast<double>(start->first);
        mOutlierDistribution.Sd = 0;
    }
    auto sqr = [](auto x){ return x * x; };
    for (auto it = mOutlierData.begin(); it != mOutlierData.end(); ++it) {
        // no surprise occurred:
        // the new element is a small number of standard deviations from the mean
        if ((fabs(it->first - mPeakDetectorMean) < kStddevThreshold * mPeakDetectorSd) ||
        if ((fabs(it->first - mOutlierDistribution.Mean) <
             mOutlierDistribution.kMaxDeviation * mOutlierDistribution.Sd) ||
             // or: right after peak has been detected, the delta is smaller than average
            (mPeakDetectorSd == 0 && fabs(it->first - mPeakDetectorMean) < kTypicalDiff)) {
            (mOutlierDistribution.Sd == 0 &&
                     fabs(it->first - mOutlierDistribution.Mean) < kTypicalDiff)) {
            // update the mean and sd:
            // count number of elements (distance between start interator and current)
            const int kN = std::distance(start, it) + 1;
            // usual formulas for mean and sd
            mPeakDetectorMean = std::accumulate(start, it + 1, 0.0,
            mOutlierDistribution.Mean = std::accumulate(start, it + 1, 0.0,
                                   [](auto &a, auto &b){return a + b.first;}) / kN;
            mPeakDetectorSd = sqrt(std::accumulate(start, it + 1, 0.0,
                      [=](auto &a, auto &b){ return a + sqr(b.first - mPeakDetectorMean);})) /
            mOutlierDistribution.Sd = sqrt(std::accumulate(start, it + 1, 0.0,
                    [=](auto &a, auto &b){
                    return a + sqr(b.first - mOutlierDistribution.Mean);})) /
                    ((kN > 1)? kN - 1 : kN); // kN - 1: mean is correlated with variance
        }
        // 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() >= kPeakSeriesSize) {
                mPeakTimestamps.pop_front();
            mPeakTimestamps.emplace_front(it->second);
            // TODO: turn this into a circular buffer
            if (mPeakTimestamps.size() >= kMaxLength.Peaks) {
                mPeakTimestamps.resize(kMaxLength.Peaks);
            }
            mPeakDetectorMean = static_cast<double>(it->first);
            mPeakDetectorSd = 0;
            mOutlierDistribution.Mean = static_cast<double>(it->first);
            mOutlierDistribution.Sd = 0;
            start = it;
        }
    }
@@ -190,7 +193,8 @@ void PerformanceAnalysis::detectPeaks() {

// Called by LogTsEntry. The input is a vector of timestamps.
// Finds outliers and writes to mOutlierdata.
// Each value in mOutlierdata consists of: <outlier timestamp, time elapsed since previous outlier>.
// Each value in mOutlierdata consists of: <outlier timestamp,
// time elapsed since previous outlier>.
// e.g. timestamps (ms) 1, 4, 5, 16, 18, 28 will produce pairs (4, 5), (13, 18).
// This function is applied to the time series before it is converted into a histogram.
void PerformanceAnalysis::storeOutlierData(const std::vector<int64_t> &timestamps) {
@@ -198,41 +202,28 @@ void PerformanceAnalysis::storeOutlierData(const std::vector<int64_t> &timestamp
        return;
    }
    // first pass: need to initialize
    if (mElapsed == 0) {
        mPrevNs = timestamps[0];
    if (mOutlierDistribution.Elapsed == 0) {
        mOutlierDistribution.PrevNs = timestamps[0];
    }
    for (const auto &ts: timestamps) {
        const uint64_t diffMs = static_cast<uint64_t>(deltaMs(mPrevNs, ts));
        const uint64_t diffMs = static_cast<uint64_t>(deltaMs(mOutlierDistribution.PrevNs, ts));
        if (diffMs >= static_cast<uint64_t>(kOutlierMs)) {
            mOutlierData.emplace_back(mElapsed, static_cast<uint64_t>(mPrevNs));
            mOutlierData.emplace_front(mOutlierDistribution.Elapsed,
                                      static_cast<uint64_t>(mOutlierDistribution.PrevNs));
            // Remove oldest value if the vector is full
            // TODO: remove pop_front once circular buffer is in place
            // 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();
            if (mOutlierData.size() >= kMaxLength.Outliers) {
                mOutlierData.resize(kMaxLength.Outliers);
            }
            mElapsed = 0;
            mOutlierDistribution.Elapsed = 0;
        }
        mElapsed += diffMs;
        mPrevNs = ts;
        mOutlierDistribution.Elapsed += diffMs;
        mOutlierDistribution.PrevNs = ts;
    }
}

// FIXME: delete this temporary test code, recycled for various new functions
void PerformanceAnalysis::testFunction() {
    // produces values (4: 5000000), (13: 18000000)
    // ns timestamps of buffer periods
    const std::vector<int64_t>kTempTestData = {1000000, 4000000, 5000000,
                                               16000000, 18000000, 28000000};
    PerformanceAnalysis::storeOutlierData(kTempTestData);
    for (const auto &outlier: mOutlierData) {
        ALOGE("PerformanceAnalysis test %lld: %lld",
              static_cast<long long>(outlier.first), static_cast<long long>(outlier.second));
    }
    detectPeaks();
}

// TODO Make it return a std::string instead of modifying body --> is this still relevant?
// TODO consider changing all ints to uint32_t or uint64_t
// TODO: move this to ReportPerformance, probably make it a friend function of PerformanceAnalysis
@@ -293,7 +284,8 @@ void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
        const int value = 1 << row;
        body->appendFormat("%.*s", leftPadding, spaces.c_str());
        for (auto const &x : buckets) {
          body->appendFormat("%.*s%s", colWidth - 1, spaces.c_str(), x.second < value ? " " : "|");
          body->appendFormat("%.*s%s", colWidth - 1,
                             spaces.c_str(), x.second < value ? " " : "|");
        }
        body->appendFormat("\n%s", " ");
    }
@@ -319,15 +311,15 @@ void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {

}


// TODO: decide whether to use this or whether it is overkill, and it is enough
// to only treat as glitches single wakeup call intervals which are too long.
// Ultimately, glitch detection will be directly on the audio signal.
// Produces a log warning if the timing of recent buffer periods caused a glitch
// Computes sum of running window of three buffer periods
// Checks whether the buffer periods leave enough CPU time for the next one
// e.g. if a buffer period is expected to be 4 ms and a buffer requires 3 ms of CPU time,
// here are some glitch cases:
// 4 + 4 + 6 ; 5 + 4 + 5; 2 + 2 + 10
// TODO: develop this code to track changes in histogram distribution in addition
// to / instead of glitches.
void PerformanceAnalysis::alertIfGlitch(const std::vector<int64_t> &samples) {
    std::deque<int> periods(kNumBuff, kPeriodMs);
    for (size_t i = 2; i < samples.size(); ++i) { // skip first time entry
@@ -348,7 +340,7 @@ void PerformanceAnalysis::alertIfGlitch(const std::vector<int64_t> &samples) {
// writes summary of performance into specified file descriptor
void dump(int fd, int indent, PerformanceAnalysisMap &threadPerformanceAnalysis) {
    String8 body;
    const char* const kName = "/data/misc/audioserver/";
    const char* const kDirectory = "/data/misc/audioserver/";
    for (auto & thread : threadPerformanceAnalysis) {
        for (auto & hash: thread.second) {
            PerformanceAnalysis& curr = hash.second;
@@ -356,8 +348,8 @@ void dump(int fd, int indent, PerformanceAnalysisMap &threadPerformanceAnalysis)
            // write performance data to console
            curr.reportPerformance(&body);
            // write to file
            writeToFile(curr.mOutlierData, curr.mHists, kName, false,
                        thread.first, hash.first);
            writeToFile(curr.mHists, curr.mOutlierData, curr.mPeakTimestamps,
                        kDirectory, false, thread.first, hash.first);
        }
    }
    if (!body.isEmpty()) {
+33 −19
Original line number Diff line number Diff line
@@ -38,10 +38,10 @@ 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(const std::deque<std::pair<outlierInterval, timestamp>> &outlierData,
                                    const std::deque<std::pair<timestamp, Histogram>> &hists,
                                    const char * kDirectory,
                                    bool append, int author, log_hash_t hash) {
void writeToFile(const std::deque<std::pair<timestamp, Histogram>> &hists,
                 const std::deque<std::pair<outlierInterval, timestamp>> &outlierData,
                 const std::deque<timestamp> &peakTimestamps,
                 const char * directory, bool append, int author, log_hash_t hash) {
    if (outlierData.empty() || hists.empty()) {
        ALOGW("No data, returning.");
        return;
@@ -49,24 +49,14 @@ void writeToFile(const std::deque<std::pair<outlierInterval, timestamp>> &outlie

    std::stringstream outlierName;
    std::stringstream histogramName;
    std::stringstream peakName;

    outlierName << kDirectory << "outliers_" << author << "_" << hash;
    histogramName << kDirectory << "histograms_" << author << "_" << hash;

    std::ofstream ofs;
    ofs.open(outlierName.str().c_str(), append ? std::ios::app : std::ios::trunc);
    if (!ofs.is_open()) {
        ALOGW("couldn't open file %s", outlierName.str().c_str());
        return;
    }
    ofs << "Outlier data: interval and timestamp\n";
    for (const auto &outlier : outlierData) {
        ofs << outlier.first << ": " << outlier.second << "\n";
    }
    ofs.close();
    histogramName << directory << "histograms_" << author << "_" << hash;
    outlierName << directory << "outliers_" << author << "_" << hash;
    peakName << directory << "peaks_" << author << "_" << hash;

    std::ofstream hfs;
    hfs.open(histogramName.str().c_str(), append ? std::ios::app : std::ios::trunc);
    hfs.open(histogramName.str(), append ? std::ios::app : std::ios::trunc);
    if (!hfs.is_open()) {
        ALOGW("couldn't open file %s", histogramName.str().c_str());
        return;
@@ -82,6 +72,30 @@ void writeToFile(const std::deque<std::pair<outlierInterval, timestamp>> &outlie
        hfs << "\n"; // separate histograms with a newline
    }
    hfs.close();

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

    std::ofstream pfs;
    pfs.open(peakName.str(), append ? std::ios::app : std::ios::trunc);
    if (!pfs.is_open()) {
        ALOGW("couldn't open file %s", peakName.str().c_str());
        return;
    }
    pfs << "Peak data: timestamp\n";
    for (const auto &peak : peakTimestamps) {
        pfs << peak << "\n";
    }
    pfs.close();
}

} // namespace ReportPerformance
+23 −23
Original line number Diff line number Diff line
@@ -79,11 +79,10 @@ public:
    // FIXME: move this data visualization to a separate class. Model/view/controller
    void reportPerformance(String8 *body, int maxHeight = 10);

    // TODO: delete this. temp for testing
    void testFunction();

    // This function used to detect glitches in a time series
    // TODO incorporate this into the analysis (currently unused)
    // This function detects glitches in a time series.
    // TODO: decide whether to use this or whether it is overkill, and it is enough
    // to only treat as glitches single wakeup call intervals which are too long.
    // Ultimately, glitch detection will be directly on the audio signal.
    void alertIfGlitch(const std::vector<timestamp_raw> &samples);

private:
@@ -103,10 +102,9 @@ private:
    // when a vector reaches its maximum size, the data is processed and flushed
    std::vector<timestamp_raw> mTimeStampSeries;

    static const int kMsPerSec = 1000;

    // Parameters used when detecting outliers
    // TODO: learn some of these from the data, delete unused ones
    // TODO: put used variables in a struct
    // FIXME: decide whether to make kPeriodMs static.
    static const int kNumBuff = 3; // number of buffers considered in local history
    int kPeriodMs; // current period length is ideally 4 ms
@@ -115,27 +113,29 @@ private:
    static constexpr double kRatio = 0.75; // estimate of CPU time as ratio of period length
    int kPeriodMsCPU; // compute based on kPeriodLen and kRatio

    // Peak detection: number of standard deviations from mean considered a significant change
    static const int kStddevThreshold = 5;

    // capacity allocated to data structures
    // TODO: make these values longer when testing is finished
    static const int kHistsCapacity = 20; // number of short-term histograms stored in memory
    static const int kHistSize = 1000; // max number of samples stored in a 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
    struct MaxLength {
        size_t Hists; // number of histograms stored in memory
        size_t TimeStamps; // histogram size, e.g. maximum length of timestamp series
        size_t Outliers; // number of values stored in outlier array
        size_t Peaks; // number of values stored in peak array
        // maximum elapsed time between first and last timestamp of a long-term histogram
    static const int kMaxHistTimespanMs = 5 * kMsPerSec;
        int HistTimespanMs;
    };
    static constexpr MaxLength kMaxLength = {.Hists = 20, .TimeStamps = 1000,
            .Outliers = 100, .Peaks = 100, .HistTimespanMs = 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.
    // FIXME: create inner class for these variables and decide which other ones to add to it
    double mPeakDetectorMean = -1;
    double mPeakDetectorSd = -1;
    // variables for storeOutlierData
    uint64_t mElapsed = 0;
    int64_t mPrevNs = -1;

    struct OutlierDistribution {
        double Mean = -1;
        double Sd = -1;
        uint64_t Elapsed = 0;
        int64_t PrevNs = -1;
        // number of standard deviations from mean considered a significant change
        const int kMaxDeviation = 5;
    } mOutlierDistribution;
};

void dump(int fd, int indent, PerformanceAnalysisMap &threadPerformanceAnalysis);
+6 −3
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ class String8;

namespace ReportPerformance {

const int kMsPerSec = 1000;

// stores a histogram: key: observed buffer period. value: count
// TODO: unsigned, unsigned
using Histogram = std::map<int, int>;
@@ -56,9 +58,10 @@ static inline uint32_t log2(uint32_t x) {

// Writes outlier intervals, timestamps, and histograms spanning long time
// intervals to a file.
void writeToFile(const std::deque<std::pair<outlierInterval, timestamp>> &outlierData,
                 const std::deque<std::pair<timestamp, Histogram>> &hists,
                 const char * kName, bool append, int author, log_hash_t hash);
void writeToFile(const std::deque<std::pair<timestamp, Histogram>> &hists,
                 const std::deque<std::pair<outlierInterval, timestamp>> &outlierData,
                 const std::deque<timestamp> &peakTimestamps,
                 const char * kDirectory, bool append, int author, log_hash_t hash);

} // namespace ReportPerformance