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