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

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

Merge "Variable time resolution in histogram (Jiffy)"

parents f53292b5 6ad40ee0
Loading
Loading
Loading
Loading
+70 −52
Original line number Diff line number Diff line
@@ -46,13 +46,6 @@ namespace android {

namespace ReportPerformance {

PerformanceAnalysis::PerformanceAnalysis() {
    // These variables will be (FIXME) learned from the data
    kPeriodMs = 4; // typical buffer period (mode)
    // average number of Ms spent processing buffer
    kPeriodMsCPU = static_cast<int>(kPeriodMs * kRatio);
}

static int widthOf(int x) {
    int width = 0;
    while (x > 0) {
@@ -68,17 +61,39 @@ static int widthOf(int x) {
// buckets the time interval into a histogram, searches for
// outliers, analyzes the outlier series for unexpectedly
// small or large values and stores these as peaks
void PerformanceAnalysis::logTsEntry(int64_t ts) {
void PerformanceAnalysis::logTsEntry(timestamp ts) {
    // after a state change, start a new series and do not
    // record time intervals in-between
    if (mOutlierDistribution.mPrevTs == 0) {
        mOutlierDistribution.mPrevTs = ts;
    if (mBufferPeriod.mPrevTs == 0) {
        mBufferPeriod.mPrevTs = ts;
        return;
    }

    // calculate time interval between current and previous timestamp
    const msInterval diffMs = static_cast<msInterval>(
        deltaMs(mBufferPeriod.mPrevTs, ts));

    const int diffJiffy = deltaJiffy(mBufferPeriod.mPrevTs, ts);


    // update buffer period distribution
    // old versus new weight ratio when updating the buffer period mean
    static constexpr double exponentialWeight = 0.999;
    // update buffer period mean with exponential weighting
    mBufferPeriod.mMean = (mBufferPeriod.mMean < 0) ? diffMs :
            exponentialWeight * mBufferPeriod.mMean + (1.0 - exponentialWeight) * diffMs;
    // set mOutlierFactor to a smaller value for the fastmixer thread
    const int kFastMixerMax = 10;
    // NormalMixer times vary much more than FastMixer times.
    // TODO: mOutlierFactor values are set empirically based on what appears to be
    // an outlier. Learn these values from the data.
    mBufferPeriod.mOutlierFactor = mBufferPeriod.mMean < kFastMixerMax ? 1.8 : 2.5;
    // set outlier threshold
    mBufferPeriod.mOutlier = mBufferPeriod.mMean * mBufferPeriod.mOutlierFactor;

    // Check whether the time interval between the current timestamp
    // and the previous one is long enough to count as an outlier
    const bool isOutlier = detectAndStoreOutlier(ts);
    const bool isOutlier = detectAndStoreOutlier(diffMs);
    // If an outlier was found, check whether it was a peak
    if (isOutlier) {
        /*bool isPeak =*/ detectAndStorePeak(
@@ -93,7 +108,7 @@ void PerformanceAnalysis::logTsEntry(int64_t ts) {
    // if the current histogram has spanned its maximum time interval.
    if (mHists.empty() ||
        deltaMs(mHists[0].first, ts) >= kMaxLength.HistTimespanMs) {
        mHists.emplace_front(static_cast<uint64_t>(ts), std::map<int, int>());
        mHists.emplace_front(ts, std::map<int, int>());
        // When memory is full, delete oldest histogram
        // TODO: use a circular buffer
        if (mHists.size() >= kMaxLength.Hists) {
@@ -101,16 +116,16 @@ void PerformanceAnalysis::logTsEntry(int64_t ts) {
        }
    }
    // add current time intervals to histogram
    ++mHists[0].second[deltaMs(mOutlierDistribution.mPrevTs, ts)];
    ++mHists[0].second[diffJiffy];
    // update previous timestamp
    mOutlierDistribution.mPrevTs = ts;
    mBufferPeriod.mPrevTs = ts;
}


// forces short-term histogram storage to avoid adding idle audio time interval
// to buffer period data
void PerformanceAnalysis::handleStateChange() {
    mOutlierDistribution.mPrevTs = 0;
    mBufferPeriod.mPrevTs = 0;
    return;
}

@@ -122,7 +137,7 @@ void PerformanceAnalysis::handleStateChange() {
// them as long as no peak is detected. When a value is more than 'threshold'
// standard deviations from the mean, a peak is detected and the mean and sigma
// are set to the peak value and 0.
bool PerformanceAnalysis::detectAndStorePeak(outlierInterval diff, timestamp ts) {
bool PerformanceAnalysis::detectAndStorePeak(msInterval diff, timestamp ts) {
    bool isPeak = false;
    if (mOutlierData.empty()) {
        return false;
@@ -135,15 +150,15 @@ bool PerformanceAnalysis::detectAndStorePeak(outlierInterval diff, timestamp ts)

    // Initialize short-term mean at start of program
    if (mOutlierDistribution.mMean == 0) {
        mOutlierDistribution.mMean = static_cast<double>(diff);
        mOutlierDistribution.mMean = diff;
    }
    // Update length of current sequence of outliers
    mOutlierDistribution.mN++;

    // If statement checks whether a large deviation from the mean occurred.
    // Check whether a large deviation from the mean occurred.
    // If the standard deviation has been reset to zero, the comparison is
    // instead to the mean of the full mOutlierInterval sequence.
    if ((fabs(static_cast<double>(diff) - mOutlierDistribution.mMean) <
    if ((fabs(diff - mOutlierDistribution.mMean) <
            mOutlierDistribution.kMaxDeviation * mOutlierDistribution.mSd) ||
            (mOutlierDistribution.mSd == 0 &&
            fabs(diff - mOutlierDistribution.mMean) <
@@ -176,7 +191,6 @@ bool PerformanceAnalysis::detectAndStorePeak(outlierInterval diff, timestamp ts)
        mOutlierDistribution.mN = 0;
        mOutlierDistribution.mM2 = 0;
    }
    ALOGD("outlier distr %f %f", mOutlierDistribution.mMean, mOutlierDistribution.mSd);
    return isPeak;
}

@@ -186,13 +200,12 @@ bool PerformanceAnalysis::detectAndStorePeak(outlierInterval diff, timestamp ts)
// and writes to mOutlierdata in the following format:
// Time elapsed since previous outlier: Timestamp of start of outlier
// e.g. timestamps (ms) 1, 4, 5, 16, 18, 28 will produce pairs (4, 5), (13, 18).
bool PerformanceAnalysis::detectAndStoreOutlier(const int64_t ts) {
bool PerformanceAnalysis::detectAndStoreOutlier(const msInterval diffMs) {
    bool isOutlier = false;
    const int64_t diffMs = static_cast<int64_t>(deltaMs(mOutlierDistribution.mPrevTs, ts));
    if (diffMs >= static_cast<int64_t>(kOutlierMs)) {
    if (diffMs >= mBufferPeriod.mOutlier) {
        isOutlier = true;
        mOutlierData.emplace_front(mOutlierDistribution.mElapsed,
                                  static_cast<uint64_t>(mOutlierDistribution.mPrevTs));
        mOutlierData.emplace_front(
                mOutlierDistribution.mElapsed, mBufferPeriod.mPrevTs);
        // Remove oldest value if the vector is full
        // TODO: turn this into a circular buffer
        // TODO: make sure kShortHistSize is large enough that that data will never be lost
@@ -206,13 +219,15 @@ bool PerformanceAnalysis::detectAndStoreOutlier(const int64_t ts) {
    return isOutlier;
}

// computes the column width required for a specific histogram value
inline int numberWidth(int number, int leftPadding) {
    return std::max(std::max(widthOf(number) + 4, 3), leftPadding + 2);
}

// 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 Make it return a std::string instead of modifying body
// TODO: move this to ReportPerformance, probably make it a friend function of PerformanceAnalysis
void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
    if (mHists.empty()) {
        ALOGD("reportPerformance: mHists is empty");
        return;
    }

@@ -224,7 +239,7 @@ void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
    }

    // underscores and spaces length corresponds to maximum width of histogram
    static const int kLen = 40;
    static const int kLen = 100;
    std::string underscores(kLen, '_');
    std::string spaces(kLen, ' ');

@@ -242,7 +257,7 @@ void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
    }
    int height = log2(maxCount) + 1; // maxCount > 0, safe to call log2
    const int leftPadding = widthOf(1 << height);
    const int colWidth = std::max(std::max(widthOf(maxDelta) + 1, 3), leftPadding + 2);
    const int bucketWidth = numberWidth(maxDelta, leftPadding);
    int scalingFactor = 1;
    // scale data if it exceeds maximum height
    if (height > maxHeight) {
@@ -255,6 +270,7 @@ void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
    body->appendFormat("\n%s", " ");
    body->appendFormat("%*s", leftPadding, " ");
    for (auto const &x : buckets) {
        const int colWidth = numberWidth(x.first / kJiffyPerMs, leftPadding);
        body->appendFormat("%*d", colWidth, x.second);
    }
    // write histogram ascii art
@@ -263,6 +279,7 @@ void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
        const int value = 1 << row;
        body->appendFormat("%.*s", leftPadding, spaces.c_str());
        for (auto const &x : buckets) {
            const int colWidth = numberWidth(x.first / kJiffyPerMs, leftPadding);
            body->appendFormat("%.*s%s", colWidth - 1,
                               spaces.c_str(), x.second < value ? " " : "|");
        }
@@ -271,15 +288,17 @@ void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
    // print x-axis
    const int columns = static_cast<int>(buckets.size());
    body->appendFormat("%*c", leftPadding, ' ');
    body->appendFormat("%.*s", (columns + 1) * colWidth, underscores.c_str());
    body->appendFormat("%.*s", (columns + 1) * bucketWidth, underscores.c_str());
    body->appendFormat("\n%s", " ");

    // write footer with bucket labels
    body->appendFormat("%*s", leftPadding, " ");
    for (auto const &x : buckets) {
        body->appendFormat("%*d", colWidth, x.first);
        const int colWidth = numberWidth(x.first / kJiffyPerMs, leftPadding);
        body->appendFormat("%*.*f", colWidth, 1,
                           static_cast<double>(x.first) / kJiffyPerMs);
    }
    body->appendFormat("%.*s%s", colWidth, spaces.c_str(), "ms\n");
    body->appendFormat("%.*s%s", bucketWidth, spaces.c_str(), "ms\n");

    // Now report glitches
    body->appendFormat("\ntime elapsed between glitches and glitch timestamps\n");
@@ -289,29 +308,28 @@ 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.
// TODO: learn what timestamp sequences correlate with glitches instead of
// manually designing a heuristic. Ultimately, detect glitches directly from audio.
// 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
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
        periods.push_front(deltaMs(samples[i - 1], samples[i]));
        periods.pop_back();
        // TODO: check that all glitch cases are covered
        if (std::accumulate(periods.begin(), periods.end(), 0) > kNumBuff * kPeriodMs +
            kPeriodMs - kPeriodMsCPU) {
                periods.assign(kNumBuff, kPeriodMs);
        }
    }
    return;
}

// 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
//      periods.push_front(deltaMs(samples[i - 1], samples[i]));
//      periods.pop_back();
//      // TODO: check that all glitch cases are covered
//      if (std::accumulate(periods.begin(), periods.end(), 0) > kNumBuff * kPeriodMs +
//          kPeriodMs - kPeriodMsCPU) {
//              periods.assign(kNumBuff, kPeriodMs);
//      }
//  }
//  return;
//}

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

+4 −3
Original line number Diff line number Diff line
@@ -39,7 +39,7 @@ 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<timestamp, Histogram>> &hists,
                 const std::deque<std::pair<outlierInterval, timestamp>> &outlierData,
                 const std::deque<std::pair<msInterval, timestamp>> &outlierData,
                 const std::deque<timestamp> &peakTimestamps,
                 const char * directory, bool append, int author, log_hash_t hash) {
    if (outlierData.empty() || hists.empty()) {
@@ -65,9 +65,10 @@ void writeToFile(const std::deque<std::pair<timestamp, Histogram>> &hists,
    for (const auto &hist : hists) {
        hfs << "\ttimestamp\n";
        hfs << hist.first << "\n";
        hfs << "\tbuckets and counts\n";
        hfs << "\tbuckets (in ms) and counts\n";
        for (const auto &bucket : hist.second) {
            hfs << bucket.first << ": " << bucket.second << "\n";
            hfs << bucket.first / static_cast<double>(kJiffyPerMs)
                    << ": " << bucket.second << "\n";
        }
        hfs << "\n"; // separate histograms with a newline
    }
+1 −2
Original line number Diff line number Diff line
@@ -39,7 +39,7 @@ class NBLog {

public:

    typedef uint64_t log_hash_t;
    using log_hash_t = ReportPerformance::log_hash_t;

    // FIXME Everything needed for client (writer API and registration) should be isolated
    //       from the rest of the implementation.
@@ -142,7 +142,6 @@ private:
        static std::unique_ptr<AbstractEntry> buildEntry(const uint8_t *ptr);

        // get format entry timestamp
        // TODO consider changing to uint64_t
        virtual int64_t      timestamp() const = 0;

        // get format entry's unique id
+35 −33
Original line number Diff line number Diff line
@@ -14,7 +14,8 @@
 * limitations under the License.
 */

// Non-blocking event logger intended for safe communication between processes via shared memory
// Non-blocking event logger intended for safe communication between
// processes via shared memory

#ifndef ANDROID_MEDIA_PERFORMANCEANALYSIS_H
#define ANDROID_MEDIA_PERFORMANCEANALYSIS_H
@@ -42,7 +43,7 @@ class PerformanceAnalysis {
    // the fifo writer utilities.
public:

    PerformanceAnalysis();
    PerformanceAnalysis() {};

    friend void dump(int fd, int indent,
                     PerformanceAnalysisMap &threadPerformanceAnalysis);
@@ -61,29 +62,28 @@ public:
    // Writes wakeup timestamp entry to log and runs analysis
    // TODO: make this thread safe. Each thread should have its own instance
    // of PerformanceAnalysis.
    void logTsEntry(timestamp_raw ts);
    void logTsEntry(timestamp ts);

    // FIXME: make peakdetector and storeOutlierData a single function
    // Input: mOutlierData. Looks at time elapsed between outliers
    // finds significant changes in the distribution
    // writes timestamps of significant changes to mPeakTimestamps
    bool detectAndStorePeak(outlierInterval delta, timestamp ts);
    bool detectAndStorePeak(msInterval delta, timestamp ts);

    // runs analysis on timestamp series before it is converted to a histogram
    // finds outliers
    // writes to mOutlierData <time elapsed since previous outlier, outlier timestamp>
    bool detectAndStoreOutlier(const timestamp_raw timestamp);
    bool detectAndStoreOutlier(const msInterval diffMs);

    // input: series of short histograms. Generates a string of analysis of the buffer periods
    // Generates a string of analysis of the buffer periods and prints to console
    // TODO: WIP write more detailed analysis
    // FIXME: move this data visualization to a separate class. Model/view/controller
    void reportPerformance(String8 *body, int maxHeight = 10);

    // 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);
    // TODO: learn what timestamp sequences correlate with glitches instead of
    // manually designing a heuristic. Ultimately, detect glitches directly from audio.
    // void alertIfGlitch(const std::vector<timestamp_raw> &samples);

private:

@@ -91,7 +91,7 @@ private:

    // stores outlier analysis:
    // <elapsed time between outliers in ms, outlier beginning timestamp>
    std::deque<std::pair<outlierInterval, timestamp>> mOutlierData;
    std::deque<std::pair<msInterval, timestamp>> mOutlierData;

    // stores each timestamp at which a peak was detected
    // a peak is a moment at which the average outlier interval changed significantly
@@ -101,43 +101,45 @@ private:
    std::deque<std::pair<timestamp, Histogram>> mHists;

    // Parameters used when detecting outliers
    // TODO: learn some of these from the data, delete unused ones
    // TODO: put used variables in a struct
    struct BufferPeriod {
        double    mMean = -1;          // average time between audio processing wakeups
        double    mOutlierFactor = -1; // values > mMean * mOutlierFactor are outliers
        double    mOutlier = -1;       // this is set to mMean * mOutlierFactor
        timestamp mPrevTs = -1;        // previous timestamp
    } mBufferPeriod;

    // TODO: delete values below if unused or add them to the BufferPeriod 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
    static const int kOutlierMs = 7; // values greater or equal to this cause glitches
    // 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
    // DAC processing time for 4 ms buffer
    static constexpr double kRatio = 0.75; // estimate of CPU time as ratio of period length
    int kPeriodMsCPU; // compute based on kPeriodLen and kRatio
    // static constexpr double kRatio = 0.75; // estimate CPU time as ratio of period
    // int kPeriodMsCPU; // compute based on kPeriodLen and kRatio

    // capacity allocated to data structures
    // TODO: make these values longer when testing is finished
    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 TimeStamps; // histogram size
        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
        int HistTimespanMs;
        int HistTimespanMs; // maximum histogram timespan
    };
    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.
    // TODO: use m prefix
    // these variables ensure continuity while analyzing the timestamp
    // series one sample at a time.
    // TODO: change this to a running variance/mean class
    struct OutlierDistribution {
        double mMean = 0; // sample mean since previous peak
        double mSd = 0; // sample sd since previous peak
        outlierInterval mElapsed = 0; // time since previous detected outlier
        int64_t mPrevTs = -1; // previous timestamp
        // number of standard deviations from mean considered a significant change
        const int kMaxDeviation = 5;
        double mTypicalDiff = 0; // global mean of outliers
        msInterval mMean = 0;         // sample mean since previous peak
        msInterval mSd = 0;           // sample sd since previous peak
        msInterval mElapsed = 0;      // time since previous detected outlier
        const int  kMaxDeviation = 5; // standard deviations from the mean threshold
        msInterval mTypicalDiff = 0;  // global mean of outliers
        double     mN = 0;            // length of sequence since the last peak
        double mM2 = 0;
        double     mM2 = 0;           // used to calculate sd
    } mOutlierDistribution;
};

+15 −13
Original line number Diff line number Diff line
@@ -29,37 +29,39 @@ class String8;

namespace ReportPerformance {

const int kMsPerSec = 1000;
constexpr int kMsPerSec = 1000;

// stores a histogram: key: observed buffer period. value: count
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
// TODO: unsigned, unsigned
using Histogram = std::map<int, int>;

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

using timestamp = int64_t;

// FIXME: decide whether to use 64 or 32 bits
// TODO: the code has a mix of typedef and using. Standardize to one or the other.
typedef uint64_t log_hash_t;
using log_hash_t = uint64_t;

// TODO: should this return an int64_t?
static inline int deltaMs(int64_t ns1, int64_t ns2) {
    return (ns2 - ns1) / (1000 * 1000);
}

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

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

// TODO: delete dump in NBLog::Reader and add it here

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