Home | History | Annotate | Download | only in libnbaio
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 
     18 #define LOG_TAG "PerformanceAnalysis"
     19 // #define LOG_NDEBUG 0
     20 
     21 #include <algorithm>
     22 #include <climits>
     23 #include <deque>
     24 #include <iostream>
     25 #include <math.h>
     26 #include <numeric>
     27 #include <vector>
     28 #include <stdarg.h>
     29 #include <stdint.h>
     30 #include <stdio.h>
     31 #include <string.h>
     32 #include <sys/prctl.h>
     33 #include <time.h>
     34 #include <new>
     35 #include <audio_utils/roundup.h>
     36 #include <media/nbaio/NBLog.h>
     37 #include <media/nbaio/PerformanceAnalysis.h>
     38 #include <media/nbaio/ReportPerformance.h>
     39 // #include <utils/CallStack.h> // used to print callstack
     40 #include <utils/Log.h>
     41 #include <utils/String8.h>
     42 
     43 #include <queue>
     44 #include <utility>
     45 
     46 namespace android {
     47 
     48 namespace ReportPerformance {
     49 
     50 PerformanceAnalysis::PerformanceAnalysis() {
     51     // These variables will be (FIXME) learned from the data
     52     kPeriodMs = 4; // typical buffer period (mode)
     53     // average number of Ms spent processing buffer
     54     kPeriodMsCPU = static_cast<int>(kPeriodMs * kRatio);
     55 }
     56 
     57 // converts a time series into a map. key: buffer period length. value: count
     58 static std::map<int, int> buildBuckets(const std::vector<int64_t> &samples) {
     59     // TODO allow buckets of variable resolution
     60     std::map<int, int> buckets;
     61     for (size_t i = 1; i < samples.size(); ++i) {
     62         ++buckets[deltaMs(samples[i - 1], samples[i])];
     63     }
     64     return buckets;
     65 }
     66 
     67 static int widthOf(int x) {
     68     int width = 0;
     69     while (x > 0) {
     70         ++width;
     71         x /= 10;
     72     }
     73     return width;
     74 }
     75 
     76 // Given a series of audio processing wakeup timestamps,
     77 // buckets the time intervals into a histogram, searches for
     78 // outliers, analyzes the outlier series for unexpectedly
     79 // small or large values and stores these as peaks, and flushes
     80 // the timestamp series from memory.
     81 void PerformanceAnalysis::processAndFlushTimeStampSeries() {
     82     // 1) analyze the series to store all outliers and their exact timestamps:
     83     storeOutlierData(mTimeStampSeries);
     84 
     85     // 2) detect peaks in the outlier series
     86     detectPeaks();
     87 
     88     // 3) compute its histogram, append to mRecentHists and clear the time series
     89     mRecentHists.emplace_back(static_cast<timestamp>(mTimeStampSeries[0]),
     90                               buildBuckets(mTimeStampSeries));
     91     // do not let mRecentHists exceed capacity
     92     // ALOGD("mRecentHists size: %d", static_cast<int>(mRecentHists.size()));
     93     if (mRecentHists.size() >= kRecentHistsCapacity) {
     94         //  ALOGD("popped back mRecentHists");
     95         mRecentHists.pop_front();
     96     }
     97     mTimeStampSeries.clear();
     98 }
     99 
    100 // forces short-term histogram storage to avoid adding idle audio time interval
    101 // to buffer period data
    102 void PerformanceAnalysis::handleStateChange() {
    103     ALOGD("handleStateChange");
    104     processAndFlushTimeStampSeries();
    105     return;
    106 }
    107 
    108 // Takes a single buffer period timestamp entry information and stores it in a
    109 // temporary series of timestamps. Once the series is full, the data is analyzed,
    110 // stored, and emptied.
    111 void PerformanceAnalysis::logTsEntry(int64_t ts) {
    112     // TODO might want to filter excessively high outliers, which are usually caused
    113     // by the thread being inactive.
    114     // Store time series data for each reader in order to bucket it once there
    115     // is enough data. Then, write to recentHists as a histogram.
    116     mTimeStampSeries.push_back(ts);
    117     // if length of the time series has reached kShortHistSize samples,
    118     // analyze the data and flush the timestamp series from memory
    119     if (mTimeStampSeries.size() >= kShortHistSize) {
    120         processAndFlushTimeStampSeries();
    121     }
    122 }
    123 
    124 // When the short-term histogram array mRecentHists has reached capacity,
    125 // merge histograms for data compression and store them in mLongTermHists
    126 // clears mRecentHists
    127 // TODO: have logTsEntry write directly to mLongTermHists, discard mRecentHists,
    128 // start a new histogram when a peak occurs
    129 void PerformanceAnalysis::processAndFlushRecentHists() {
    130 
    131     // Buckets is used to aggregate short-term histograms.
    132     Histogram buckets;
    133     timestamp startingTs = mRecentHists[0].first;
    134 
    135     for (const auto &shortHist: mRecentHists) {
    136         // If the time between starting and ending timestamps has reached the maximum,
    137         // add the current histogram (buckets) to the long-term histogram buffer,
    138         // clear buckets, and start a new long-term histogram aggregation process.
    139         if (deltaMs(startingTs, shortHist.first) >= kMaxHistTimespanMs) {
    140             mLongTermHists.emplace_back(startingTs, std::move(buckets));
    141             buckets.clear();
    142             startingTs = shortHist.first;
    143             // When memory is full, delete oldest histogram
    144             // TODO use a circular buffer
    145             if (mLongTermHists.size() >= kLongTermHistsCapacity) {
    146                 mLongTermHists.pop_front();
    147             }
    148         }
    149 
    150         // add current histogram to buckets
    151         for (const auto &countPair : shortHist.second) {
    152             buckets[countPair.first] += countPair.second;
    153         }
    154     }
    155     mRecentHists.clear();
    156     // TODO: decide when/where to call writeToFile
    157     // TODO: add a thread-specific extension to the file name
    158     static const char* const kName = (const char *) "/data/misc/audioserver/sample_results.txt";
    159     writeToFile(mOutlierData, mLongTermHists, kName, false);
    160 }
    161 
    162 // Given a series of outlier intervals (mOutlier data),
    163 // looks for changes in distribution (peaks), which can be either positive or negative.
    164 // The function sets the mean to the starting value and sigma to 0, and updates
    165 // them as long as no peak is detected. When a value is more than 'threshold'
    166 // standard deviations from the mean, a peak is detected and the mean and sigma
    167 // are set to the peak value and 0.
    168 void PerformanceAnalysis::detectPeaks() {
    169     if (mOutlierData.empty()) {
    170         return;
    171     }
    172 
    173     // compute mean of the distribution. Used to check whether a value is large
    174     const double kTypicalDiff = std::accumulate(
    175         mOutlierData.begin(), mOutlierData.end(), 0,
    176         [](auto &a, auto &b){return a + b.first;}) / mOutlierData.size();
    177     // ALOGD("typicalDiff %f", kTypicalDiff);
    178 
    179     // iterator at the beginning of a sequence, or updated to the most recent peak
    180     std::deque<std::pair<uint64_t, uint64_t>>::iterator start = mOutlierData.begin();
    181     // the mean and standard deviation are updated every time a peak is detected
    182     // initialize first time. The mean from the previous sequence is stored
    183     // for the next sequence. Here, they are initialized for the first time.
    184     if (mPeakDetectorMean < 0) {
    185         mPeakDetectorMean = static_cast<double>(start->first);
    186         mPeakDetectorSd = 0;
    187     }
    188     auto sqr = [](auto x){ return x * x; };
    189     for (auto it = mOutlierData.begin(); it != mOutlierData.end(); ++it) {
    190         // no surprise occurred:
    191         // the new element is a small number of standard deviations from the mean
    192         if ((fabs(it->first - mPeakDetectorMean) < kStddevThreshold * mPeakDetectorSd) ||
    193              // or: right after peak has been detected, the delta is smaller than average
    194             (mPeakDetectorSd == 0 && fabs(it->first - mPeakDetectorMean) < kTypicalDiff)) {
    195             // update the mean and sd:
    196             // count number of elements (distance between start interator and current)
    197             const int kN = std::distance(start, it) + 1;
    198             // usual formulas for mean and sd
    199             mPeakDetectorMean = std::accumulate(start, it + 1, 0.0,
    200                                    [](auto &a, auto &b){return a + b.first;}) / kN;
    201             mPeakDetectorSd = sqrt(std::accumulate(start, it + 1, 0.0,
    202                       [=](auto &a, auto &b){ return a + sqr(b.first - mPeakDetectorMean);})) /
    203                       ((kN > 1)? kN - 1 : kN); // kN - 1: mean is correlated with variance
    204         }
    205         // surprising value: store peak timestamp and reset mean, sd, and start iterator
    206         else {
    207             mPeakTimestamps.emplace_back(it->second);
    208             // TODO: remove pop_front once a circular buffer is in place
    209             if (mPeakTimestamps.size() >= kPeakSeriesSize) {
    210                 mPeakTimestamps.pop_front();
    211             }
    212             mPeakDetectorMean = static_cast<double>(it->first);
    213             mPeakDetectorSd = 0;
    214             start = it;
    215         }
    216     }
    217     return;
    218 }
    219 
    220 // Called by LogTsEntry. The input is a vector of timestamps.
    221 // Finds outliers and writes to mOutlierdata.
    222 // Each value in mOutlierdata consists of: <outlier timestamp, time elapsed since previous outlier>.
    223 // e.g. timestamps (ms) 1, 4, 5, 16, 18, 28 will produce pairs (4, 5), (13, 18).
    224 // This function is applied to the time series before it is converted into a histogram.
    225 void PerformanceAnalysis::storeOutlierData(const std::vector<int64_t> &timestamps) {
    226     if (timestamps.size() < 1) {
    227         return;
    228     }
    229     // first pass: need to initialize
    230     if (mElapsed == 0) {
    231         mPrevNs = timestamps[0];
    232     }
    233     for (const auto &ts: timestamps) {
    234         const uint64_t diffMs = static_cast<uint64_t>(deltaMs(mPrevNs, ts));
    235         if (diffMs >= static_cast<uint64_t>(kOutlierMs)) {
    236             mOutlierData.emplace_back(mElapsed, static_cast<uint64_t>(mPrevNs));
    237             // Remove oldest value if the vector is full
    238             // TODO: remove pop_front once circular buffer is in place
    239             // FIXME: make sure kShortHistSize is large enough that that data will never be lost
    240             // before being written to file or to a FIFO
    241             if (mOutlierData.size() >= kOutlierSeriesSize) {
    242                 mOutlierData.pop_front();
    243             }
    244             mElapsed = 0;
    245         }
    246         mElapsed += diffMs;
    247         mPrevNs = ts;
    248     }
    249 }
    250 
    251 
    252 // FIXME: delete this temporary test code, recycled for various new functions
    253 void PerformanceAnalysis::testFunction() {
    254     // produces values (4: 5000000), (13: 18000000)
    255     // ns timestamps of buffer periods
    256     const std::vector<int64_t>kTempTestData = {1000000, 4000000, 5000000,
    257                                                16000000, 18000000, 28000000};
    258     PerformanceAnalysis::storeOutlierData(kTempTestData);
    259     for (const auto &outlier: mOutlierData) {
    260         ALOGE("PerformanceAnalysis test %lld: %lld",
    261               static_cast<long long>(outlier.first), static_cast<long long>(outlier.second));
    262     }
    263     detectPeaks();
    264 }
    265 
    266 // TODO Make it return a std::string instead of modifying body --> is this still relevant?
    267 // TODO consider changing all ints to uint32_t or uint64_t
    268 // TODO: move this to ReportPerformance, probably make it a friend function of PerformanceAnalysis
    269 void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
    270     if (mRecentHists.size() < 1) {
    271         ALOGD("reportPerformance: mRecentHists is empty");
    272         return;
    273     }
    274     ALOGD("reportPerformance: hists size %d", static_cast<int>(mRecentHists.size()));
    275     // TODO: more elaborate data analysis
    276     std::map<int, int> buckets;
    277     for (const auto &shortHist: mRecentHists) {
    278         for (const auto &countPair : shortHist.second) {
    279             buckets[countPair.first] += countPair.second;
    280         }
    281     }
    282 
    283     // underscores and spaces length corresponds to maximum width of histogram
    284     static const int kLen = 40;
    285     std::string underscores(kLen, '_');
    286     std::string spaces(kLen, ' ');
    287 
    288     auto it = buckets.begin();
    289     int maxDelta = it->first;
    290     int maxCount = it->second;
    291     // Compute maximum values
    292     while (++it != buckets.end()) {
    293         if (it->first > maxDelta) {
    294             maxDelta = it->first;
    295         }
    296         if (it->second > maxCount) {
    297             maxCount = it->second;
    298         }
    299     }
    300     int height = log2(maxCount) + 1; // maxCount > 0, safe to call log2
    301     const int leftPadding = widthOf(1 << height);
    302     const int colWidth = std::max(std::max(widthOf(maxDelta) + 1, 3), leftPadding + 2);
    303     int scalingFactor = 1;
    304     // scale data if it exceeds maximum height
    305     if (height > maxHeight) {
    306         scalingFactor = (height + maxHeight) / maxHeight;
    307         height /= scalingFactor;
    308     }
    309     body->appendFormat("\n%*s", leftPadding + 11, "Occurrences");
    310     // write histogram label line with bucket values
    311     body->appendFormat("\n%s", " ");
    312     body->appendFormat("%*s", leftPadding, " ");
    313     for (auto const &x : buckets) {
    314         body->appendFormat("%*d", colWidth, x.second);
    315     }
    316     // write histogram ascii art
    317     body->appendFormat("\n%s", " ");
    318     for (int row = height * scalingFactor; row >= 0; row -= scalingFactor) {
    319         const int value = 1 << row;
    320         body->appendFormat("%.*s", leftPadding, spaces.c_str());
    321         for (auto const &x : buckets) {
    322           body->appendFormat("%.*s%s", colWidth - 1, spaces.c_str(), x.second < value ? " " : "|");
    323         }
    324         body->appendFormat("\n%s", " ");
    325     }
    326     // print x-axis
    327     const int columns = static_cast<int>(buckets.size());
    328     body->appendFormat("%*c", leftPadding, ' ');
    329     body->appendFormat("%.*s", (columns + 1) * colWidth, underscores.c_str());
    330     body->appendFormat("\n%s", " ");
    331 
    332     // write footer with bucket labels
    333     body->appendFormat("%*s", leftPadding, " ");
    334     for (auto const &x : buckets) {
    335         body->appendFormat("%*d", colWidth, x.first);
    336     }
    337     body->appendFormat("%.*s%s", colWidth, spaces.c_str(), "ms\n");
    338 
    339     // Now report glitches
    340     body->appendFormat("\ntime elapsed between glitches and glitch timestamps\n");
    341     for (const auto &outlier: mOutlierData) {
    342         body->appendFormat("%lld: %lld\n", static_cast<long long>(outlier.first),
    343                            static_cast<long long>(outlier.second));
    344     }
    345 
    346 }
    347 
    348 
    349 // Produces a log warning if the timing of recent buffer periods caused a glitch
    350 // Computes sum of running window of three buffer periods
    351 // Checks whether the buffer periods leave enough CPU time for the next one
    352 // e.g. if a buffer period is expected to be 4 ms and a buffer requires 3 ms of CPU time,
    353 // here are some glitch cases:
    354 // 4 + 4 + 6 ; 5 + 4 + 5; 2 + 2 + 10
    355 // TODO: develop this code to track changes in histogram distribution in addition
    356 // to / instead of glitches.
    357 void PerformanceAnalysis::alertIfGlitch(const std::vector<int64_t> &samples) {
    358     std::deque<int> periods(kNumBuff, kPeriodMs);
    359     for (size_t i = 2; i < samples.size(); ++i) { // skip first time entry
    360         periods.push_front(deltaMs(samples[i - 1], samples[i]));
    361         periods.pop_back();
    362         // TODO: check that all glitch cases are covered
    363         if (std::accumulate(periods.begin(), periods.end(), 0) > kNumBuff * kPeriodMs +
    364             kPeriodMs - kPeriodMsCPU) {
    365                 ALOGW("A glitch occurred");
    366                 periods.assign(kNumBuff, kPeriodMs);
    367         }
    368     }
    369     return;
    370 }
    371 
    372 } // namespace ReportPerformance
    373 
    374 }   // namespace android
    375