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.) mBinSize(binSize)71 : mBinSize(binSize), mNumBins(numBins), mLow(low), mBins(mNumBins + 2) {} 72 Histogram(const Config & c)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. resetPerformanceData172 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. emptyPerformanceData183 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 PerformanceAnalysis()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