Home | History | Annotate | Download | only in nblog
      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 #ifndef ANDROID_MEDIA_PERFORMANCEANALYSIS_H
     18 #define ANDROID_MEDIA_PERFORMANCEANALYSIS_H
     19 
     20 #include <deque>
     21 #include <map>
     22 #include <string>
     23 #include <utility>
     24 #include <vector>
     25 
     26 #include <media/nblog/Events.h>
     27 #include <media/nblog/ReportPerformance.h>
     28 #include <utils/Timers.h>
     29 
     30 namespace android {
     31 
     32 class String8;
     33 
     34 namespace ReportPerformance {
     35 
     36 // TODO make this a templated class and put it in a separate file.
     37 // The templated parameters would be bin size and low limit.
     38 /*
     39  * Histogram provides a way to store numeric data in histogram format and read it as a serialized
     40  * string. The terms "bin" and "bucket" are used interchangeably.
     41  *
     42  * This class is not thread-safe.
     43  */
     44 class Histogram {
     45 public:
     46     struct Config {
     47         const double binSize;   // TODO template type
     48         const size_t numBins;
     49         const double low;       // TODO template type
     50     };
     51 
     52     // Histograms are constructed with fixed configuration numbers. Dynamic configuration based
     53     // the data is possible but complex because
     54     // - data points are added one by one, not processed as a batch.
     55     // - Histograms with different configuration parameters are tricky to aggregate, and they
     56     //   will need to be aggregated at the Media Metrics cloud side.
     57     // - not providing limits theoretically allows for infinite number of buckets.
     58 
     59     /**
     60      * \brief Creates a Histogram object.
     61      *
     62      * \param binSize the width of each bin of the histogram, must be greater than 0.
     63      *                Units are whatever data the caller decides to store.
     64      * \param numBins the number of bins desired in the histogram range, must be greater than 0.
     65      * \param low     the lower bound of the histogram bucket values.
     66      *                Units are whatever data the caller decides to store.
     67      *                Note that the upper bound can be calculated by the following:
     68      *                  upper = lower + binSize * numBins.
     69      */
     70     Histogram(double binSize, size_t numBins, double low = 0.)
     71         : mBinSize(binSize), mNumBins(numBins), mLow(low), mBins(mNumBins + 2) {}
     72 
     73     Histogram(const Config &c)
     74         : Histogram(c.binSize, c.numBins, c.low) {}
     75 
     76     /**
     77      * \brief Add a data point to the histogram. The value of the data point
     78      *        is rounded to the nearest multiple of the bin size (before accounting
     79      *        for the lower bound offset, which may not be a multiple of the bin size).
     80      *
     81      * \param value the value of the data point to add.
     82      */
     83     void add(double value);
     84 
     85     /**
     86      * \brief Removes all data points from the histogram.
     87      */
     88     void clear();
     89 
     90     /**
     91      * \brief Returns the total number of data points added to the histogram.
     92      *
     93      * \return the total number of data points in the histogram.
     94      */
     95     uint64_t totalCount() const;
     96 
     97     /**
     98      * \brief Serializes the histogram into a string. The format is chosen to be compatible with
     99      *        the histogram representation to send to the Media Metrics service.
    100      *
    101      *        The string is as follows:
    102      *          binSize,numBins,low,{-1|lowCount,...,binIndex|count,...,numBins|highCount}
    103      *
    104      *        - binIndex is an integer with 0 <= binIndex < numBins.
    105      *        - count is the number of occurrences of the (rounded) value
    106      *          low + binSize * bucketIndex.
    107      *        - lowCount is the number of (rounded) values less than low.
    108      *        - highCount is the number of (rounded) values greater than or equal to
    109      *          low + binSize * numBins.
    110      *        - a binIndex may be skipped if its count is 0.
    111      *
    112      * \return the histogram serialized as a string.
    113      */
    114     std::string toString() const;
    115 
    116     // Draw log scale sideways histogram as ASCII art and store as a std::string.
    117     // Empty string is returned if totalCount() == 0.
    118     std::string asciiArtString(size_t indent = 0) const;
    119 
    120 private:
    121     // Histogram version number.
    122     static constexpr int kVersion = 1;
    123 
    124     const double mBinSize;          // Size of each bucket
    125     const size_t mNumBins;          // Number of buckets in range (excludes low and high)
    126     const double mLow;              // Lower bound of values
    127 
    128     // Data structure to store the actual histogram. Counts of bin values less than mLow
    129     // are stored in mBins[0]. Bin index i corresponds to mBins[i+1]. Counts of bin values
    130     // >= high are stored in mBins[mNumBins + 1].
    131     std::vector<uint64_t> mBins;
    132 
    133     uint64_t mTotalCount = 0;       // Total number of values recorded
    134 };
    135 
    136 // This is essentially the same as class PerformanceAnalysis, but PerformanceAnalysis
    137 // also does some additional analyzing of data, while the purpose of this struct is
    138 // to hold data.
    139 struct PerformanceData {
    140     // TODO the Histogram::Config numbers below are for FastMixer.
    141     // Specify different numbers for other thread types.
    142 
    143     // Values based on mUnderrunNs and mOverrunNs in FastMixer.cpp for frameCount = 192
    144     // and mSampleRate = 48000, which correspond to 2 and 7 seconds.
    145     static constexpr Histogram::Config kWorkConfig = { 0.25, 20, 2.};
    146 
    147     // Values based on trial and error logging. Need a better way to determine
    148     // bin size and lower/upper limits.
    149     static constexpr Histogram::Config kLatencyConfig = { 2., 10, 10.};
    150 
    151     // Values based on trial and error logging. Need a better way to determine
    152     // bin size and lower/upper limits.
    153     static constexpr Histogram::Config kWarmupConfig = { 5., 10, 10.};
    154 
    155     NBLog::thread_info_t threadInfo{};
    156     NBLog::thread_params_t threadParams{};
    157 
    158     // Performance Data
    159     Histogram workHist{kWorkConfig};
    160     Histogram latencyHist{kLatencyConfig};
    161     Histogram warmupHist{kWarmupConfig};
    162     int64_t underruns = 0;
    163     static constexpr size_t kMaxSnapshotsToStore = 256;
    164     std::deque<std::pair<NBLog::Event, int64_t /*timestamp*/>> snapshots;
    165     int64_t overruns = 0;
    166     nsecs_t active = 0;
    167     nsecs_t start{systemTime()};
    168 
    169     // Reset the performance data. This does not represent a thread state change.
    170     // Thread info is not reset here because the data is meant to be a continuation of the thread
    171     // that struct PerformanceData is associated with.
    172     void reset() {
    173         workHist.clear();
    174         latencyHist.clear();
    175         warmupHist.clear();
    176         underruns = 0;
    177         overruns = 0;
    178         active = 0;
    179         start = systemTime();
    180     }
    181 
    182     // Return true if performance data has not been recorded yet, false otherwise.
    183     bool empty() const {
    184         return workHist.totalCount() == 0 && latencyHist.totalCount() == 0
    185                 && warmupHist.totalCount() == 0 && underruns == 0 && overruns == 0
    186                 && active == 0;
    187     }
    188 };
    189 
    190 //------------------------------------------------------------------------------
    191 
    192 class PerformanceAnalysis;
    193 
    194 // a map of PerformanceAnalysis instances
    195 // The outer key is for the thread, the inner key for the source file location.
    196 using PerformanceAnalysisMap = std::map<int, std::map<log_hash_t, PerformanceAnalysis>>;
    197 
    198 class PerformanceAnalysis {
    199     // This class stores and analyzes audio processing wakeup timestamps from NBLog
    200     // FIXME: currently, all performance data is stored in deques. Turn these into circular
    201     // buffers.
    202     // TODO: add a mutex.
    203 public:
    204 
    205     PerformanceAnalysis() {};
    206 
    207     friend void dump(int fd, int indent,
    208                      PerformanceAnalysisMap &threadPerformanceAnalysis);
    209 
    210     // Called in the case of an audio on/off event, e.g., EVENT_AUDIO_STATE.
    211     // Used to discard idle time intervals
    212     void handleStateChange();
    213 
    214     // Writes wakeup timestamp entry to log and runs analysis
    215     void logTsEntry(timestamp ts);
    216 
    217     // FIXME: make peakdetector and storeOutlierData a single function
    218     // Input: mOutlierData. Looks at time elapsed between outliers
    219     // finds significant changes in the distribution
    220     // writes timestamps of significant changes to mPeakTimestamps
    221     bool detectAndStorePeak(msInterval delta, timestamp ts);
    222 
    223     // stores timestamps of intervals above a threshold: these are assumed outliers.
    224     // writes to mOutlierData <time elapsed since previous outlier, outlier timestamp>
    225     bool detectAndStoreOutlier(const msInterval diffMs);
    226 
    227     // Generates a string of analysis of the buffer periods and prints to console
    228     // FIXME: move this data visualization to a separate class. Model/view/controller
    229     void reportPerformance(String8 *body, int author, log_hash_t hash,
    230                            int maxHeight = 10);
    231 
    232 private:
    233 
    234     // TODO use a circular buffer for the deques and vectors below
    235 
    236     // stores outlier analysis:
    237     // <elapsed time between outliers in ms, outlier beginning timestamp>
    238     std::deque<std::pair<msInterval, timestamp>> mOutlierData;
    239 
    240     // stores each timestamp at which a peak was detected
    241     // a peak is a moment at which the average outlier interval changed significantly
    242     std::deque<timestamp> mPeakTimestamps;
    243 
    244     // stores buffer period histograms with timestamp of first sample
    245     std::deque<std::pair<timestamp, Hist>> mHists;
    246 
    247     // Parameters used when detecting outliers
    248     struct BufferPeriod {
    249         double    mMean = -1;          // average time between audio processing wakeups
    250         double    mOutlierFactor = -1; // values > mMean * mOutlierFactor are outliers
    251         double    mOutlier = -1;       // this is set to mMean * mOutlierFactor
    252         timestamp mPrevTs = -1;        // previous timestamp
    253     } mBufferPeriod;
    254 
    255     // capacity allocated to data structures
    256     struct MaxLength {
    257         size_t Hists; // number of histograms stored in memory
    258         size_t Outliers; // number of values stored in outlier array
    259         size_t Peaks; // number of values stored in peak array
    260         int HistTimespanMs; // maximum histogram timespan
    261     };
    262     // These values allow for 10 hours of data allowing for a glitch and a peak
    263     // as often as every 3 seconds
    264     static constexpr MaxLength kMaxLength = {.Hists = 60, .Outliers = 12000,
    265             .Peaks = 12000, .HistTimespanMs = 10 * kSecPerMin * kMsPerSec };
    266 
    267     // these variables ensure continuity while analyzing the timestamp
    268     // series one sample at a time.
    269     // TODO: change this to a running variance/mean class
    270     struct OutlierDistribution {
    271         msInterval mMean = 0;         // sample mean since previous peak
    272         msInterval mSd = 0;           // sample sd since previous peak
    273         msInterval mElapsed = 0;      // time since previous detected outlier
    274         const int  kMaxDeviation = 5; // standard deviations from the mean threshold
    275         msInterval mTypicalDiff = 0;  // global mean of outliers
    276         double     mN = 0;            // length of sequence since the last peak
    277         double     mM2 = 0;           // used to calculate sd
    278     } mOutlierDistribution;
    279 };
    280 
    281 void dump(int fd, int indent, PerformanceAnalysisMap &threadPerformanceAnalysis);
    282 void dumpLine(int fd, int indent, const String8 &body);
    283 
    284 }   // namespace ReportPerformance
    285 }   // namespace android
    286 
    287 #endif  // ANDROID_MEDIA_PERFORMANCEANALYSIS_H
    288