• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/nblog/NBLog.h>
37 #include <media/nblog/PerformanceAnalysis.h>
38 #include <media/nblog/ReportPerformance.h>
39 #include <utils/Log.h>
40 #include <utils/String8.h>
41 
42 #include <queue>
43 #include <utility>
44 
45 namespace android {
46 
47 namespace ReportPerformance {
48 
49 // Given an audio processing wakeup timestamp, buckets the time interval
50 // since the previous timestamp into a histogram, searches for
51 // outliers, analyzes the outlier series for unexpectedly
52 // small or large values and stores these as peaks
logTsEntry(timestamp ts)53 void PerformanceAnalysis::logTsEntry(timestamp ts) {
54     // after a state change, start a new series and do not
55     // record time intervals in-between
56     if (mBufferPeriod.mPrevTs == 0) {
57         mBufferPeriod.mPrevTs = ts;
58         return;
59     }
60 
61     // calculate time interval between current and previous timestamp
62     const msInterval diffMs = static_cast<msInterval>(
63         deltaMs(mBufferPeriod.mPrevTs, ts));
64 
65     const int diffJiffy = deltaJiffy(mBufferPeriod.mPrevTs, ts);
66 
67     // old versus new weight ratio when updating the buffer period mean
68     static constexpr double exponentialWeight = 0.999;
69     // update buffer period mean with exponential weighting
70     mBufferPeriod.mMean = (mBufferPeriod.mMean < 0) ? diffMs :
71             exponentialWeight * mBufferPeriod.mMean + (1.0 - exponentialWeight) * diffMs;
72     // set mOutlierFactor to a smaller value for the fastmixer thread
73     const int kFastMixerMax = 10;
74     // NormalMixer times vary much more than FastMixer times.
75     // TODO: mOutlierFactor values are set empirically based on what appears to be
76     // an outlier. Learn these values from the data.
77     mBufferPeriod.mOutlierFactor = mBufferPeriod.mMean < kFastMixerMax ? 1.8 : 2.0;
78     // set outlier threshold
79     mBufferPeriod.mOutlier = mBufferPeriod.mMean * mBufferPeriod.mOutlierFactor;
80 
81     // Check whether the time interval between the current timestamp
82     // and the previous one is long enough to count as an outlier
83     const bool isOutlier = detectAndStoreOutlier(diffMs);
84     // If an outlier was found, check whether it was a peak
85     if (isOutlier) {
86         /*bool isPeak =*/ detectAndStorePeak(
87             mOutlierData[0].first, mOutlierData[0].second);
88         // TODO: decide whether to insert a new empty histogram if a peak
89         // TODO: remove isPeak if unused to avoid "unused variable" error
90         // occurred at the current timestamp
91     }
92 
93     // Insert a histogram to mHists if it is empty, or
94     // close the current histogram and insert a new empty one if
95     // if the current histogram has spanned its maximum time interval.
96     if (mHists.empty() ||
97         deltaMs(mHists[0].first, ts) >= kMaxLength.HistTimespanMs) {
98         mHists.emplace_front(ts, std::map<int, int>());
99         // When memory is full, delete oldest histogram
100         // TODO: use a circular buffer
101         if (mHists.size() >= kMaxLength.Hists) {
102             mHists.resize(kMaxLength.Hists);
103         }
104     }
105     // add current time intervals to histogram
106     ++mHists[0].second[diffJiffy];
107     // update previous timestamp
108     mBufferPeriod.mPrevTs = ts;
109 }
110 
111 
112 // forces short-term histogram storage to avoid adding idle audio time interval
113 // to buffer period data
handleStateChange()114 void PerformanceAnalysis::handleStateChange() {
115     mBufferPeriod.mPrevTs = 0;
116     return;
117 }
118 
119 
120 // Checks whether the time interval between two outliers is far enough from
121 // a typical delta to be considered a peak.
122 // looks for changes in distribution (peaks), which can be either positive or negative.
123 // The function sets the mean to the starting value and sigma to 0, and updates
124 // them as long as no peak is detected. When a value is more than 'threshold'
125 // standard deviations from the mean, a peak is detected and the mean and sigma
126 // are set to the peak value and 0.
detectAndStorePeak(msInterval diff,timestamp ts)127 bool PerformanceAnalysis::detectAndStorePeak(msInterval diff, timestamp ts) {
128     bool isPeak = false;
129     if (mOutlierData.empty()) {
130         return false;
131     }
132     // Update mean of the distribution
133     // TypicalDiff is used to check whether a value is unusually large
134     // when we cannot use standard deviations from the mean because the sd is set to 0.
135     mOutlierDistribution.mTypicalDiff = (mOutlierDistribution.mTypicalDiff *
136             (mOutlierData.size() - 1) + diff) / mOutlierData.size();
137 
138     // Initialize short-term mean at start of program
139     if (mOutlierDistribution.mMean == 0) {
140         mOutlierDistribution.mMean = diff;
141     }
142     // Update length of current sequence of outliers
143     mOutlierDistribution.mN++;
144 
145     // Check whether a large deviation from the mean occurred.
146     // If the standard deviation has been reset to zero, the comparison is
147     // instead to the mean of the full mOutlierInterval sequence.
148     if ((fabs(diff - mOutlierDistribution.mMean) <
149             mOutlierDistribution.kMaxDeviation * mOutlierDistribution.mSd) ||
150             (mOutlierDistribution.mSd == 0 &&
151             fabs(diff - mOutlierDistribution.mMean) <
152             mOutlierDistribution.mTypicalDiff)) {
153         // update the mean and sd using online algorithm
154         // https://en.wikipedia.org/wiki/
155         // Algorithms_for_calculating_variance#Online_algorithm
156         mOutlierDistribution.mN++;
157         const double kDelta = diff - mOutlierDistribution.mMean;
158         mOutlierDistribution.mMean += kDelta / mOutlierDistribution.mN;
159         const double kDelta2 = diff - mOutlierDistribution.mMean;
160         mOutlierDistribution.mM2 += kDelta * kDelta2;
161         mOutlierDistribution.mSd = (mOutlierDistribution.mN < 2) ? 0 :
162                 sqrt(mOutlierDistribution.mM2 / (mOutlierDistribution.mN - 1));
163     } else {
164         // new value is far from the mean:
165         // store peak timestamp and reset mean, sd, and short-term sequence
166         isPeak = true;
167         mPeakTimestamps.emplace_front(ts);
168         // if mPeaks has reached capacity, delete oldest data
169         // Note: this means that mOutlierDistribution values do not exactly
170         // match the data we have in mPeakTimestamps, but this is not an issue
171         // in practice for estimating future peaks.
172         // TODO: turn this into a circular buffer
173         if (mPeakTimestamps.size() >= kMaxLength.Peaks) {
174             mPeakTimestamps.resize(kMaxLength.Peaks);
175         }
176         mOutlierDistribution.mMean = 0;
177         mOutlierDistribution.mSd = 0;
178         mOutlierDistribution.mN = 0;
179         mOutlierDistribution.mM2 = 0;
180     }
181     return isPeak;
182 }
183 
184 
185 // Determines whether the difference between a timestamp and the previous
186 // one is beyond a threshold. If yes, stores the timestamp as an outlier
187 // and writes to mOutlierdata in the following format:
188 // Time elapsed since previous outlier: Timestamp of start of outlier
189 // e.g. timestamps (ms) 1, 4, 5, 16, 18, 28 will produce pairs (4, 5), (13, 18).
190 // TODO: learn what timestamp sequences correlate with glitches instead of
191 // manually designing a heuristic.
detectAndStoreOutlier(const msInterval diffMs)192 bool PerformanceAnalysis::detectAndStoreOutlier(const msInterval diffMs) {
193     bool isOutlier = false;
194     if (diffMs >= mBufferPeriod.mOutlier) {
195         isOutlier = true;
196         mOutlierData.emplace_front(
197                 mOutlierDistribution.mElapsed, mBufferPeriod.mPrevTs);
198         // Remove oldest value if the vector is full
199         // TODO: turn this into a circular buffer
200         // TODO: make sure kShortHistSize is large enough that that data will never be lost
201         // before being written to file or to a FIFO
202         if (mOutlierData.size() >= kMaxLength.Outliers) {
203             mOutlierData.resize(kMaxLength.Outliers);
204         }
205         mOutlierDistribution.mElapsed = 0;
206     }
207     mOutlierDistribution.mElapsed += diffMs;
208     return isOutlier;
209 }
210 
widthOf(int x)211 static int widthOf(int x) {
212     int width = 0;
213     if (x < 0) {
214         width++;
215         x = x == INT_MIN ? INT_MAX : -x;
216     }
217     // assert (x >= 0)
218     do {
219         ++width;
220         x /= 10;
221     } while (x > 0);
222     return width;
223 }
224 
225 // computes the column width required for a specific histogram value
numberWidth(double number,int leftPadding)226 inline int numberWidth(double number, int leftPadding) {
227     // Added values account for whitespaces needed around numbers, and for the
228     // dot and decimal digit not accounted for by widthOf
229     return std::max(std::max(widthOf(static_cast<int>(number)) + 3, 2), leftPadding + 1);
230 }
231 
232 // rounds value to precision based on log-distance from mean
233 __attribute__((no_sanitize("signed-integer-overflow")))
logRound(double x,double mean)234 inline double logRound(double x, double mean) {
235     // Larger values decrease range of high resolution and prevent overflow
236     // of a histogram on the console.
237     // The following formula adjusts kBase based on the buffer period length.
238     // Different threads have buffer periods ranging from 2 to 40. The
239     // formula below maps buffer period 2 to kBase = ~1, 4 to ~2, 20 to ~3, 40 to ~4.
240     // TODO: tighten this for higher means, the data still overflows
241     const double kBase = log(mean) / log(2.2);
242     const double power = floor(
243         log(abs(x - mean) / mean) / log(kBase)) + 2;
244     // do not round values close to the mean
245     if (power < 1) {
246         return x;
247     }
248     const int factor = static_cast<int>(pow(10, power));
249     return (static_cast<int>(x) * factor) / factor;
250 }
251 
252 // TODO Make it return a std::string instead of modifying body
253 // TODO: move this to ReportPerformance, probably make it a friend function
254 // of PerformanceAnalysis
reportPerformance(String8 * body,int author,log_hash_t hash,int maxHeight)255 void PerformanceAnalysis::reportPerformance(String8 *body, int author, log_hash_t hash,
256                                             int maxHeight) {
257     if (mHists.empty()) {
258         return;
259     }
260 
261     // ms of active audio in displayed histogram
262     double elapsedMs = 0;
263     // starting timestamp of histogram
264     timestamp startingTs = mHists[0].first;
265 
266     // histogram which stores .1 precision ms counts instead of Jiffy multiple counts
267     std::map<double, int> buckets;
268     for (const auto &shortHist: mHists) {
269         for (const auto &countPair : shortHist.second) {
270             const double ms = static_cast<double>(countPair.first) / kJiffyPerMs;
271             buckets[logRound(ms, mBufferPeriod.mMean)] += countPair.second;
272             elapsedMs += ms * countPair.second;
273         }
274     }
275 
276     // underscores and spaces length corresponds to maximum width of histogram
277     static const int kLen = 200;
278     std::string underscores(kLen, '_');
279     std::string spaces(kLen, ' ');
280 
281     auto it = buckets.begin();
282     double maxDelta = it->first;
283     int maxCount = it->second;
284     // Compute maximum values
285     while (++it != buckets.end()) {
286         if (it->first > maxDelta) {
287             maxDelta = it->first;
288         }
289         if (it->second > maxCount) {
290             maxCount = it->second;
291         }
292     }
293     int height = log2(maxCount) + 1; // maxCount > 0, safe to call log2
294     const int leftPadding = widthOf(1 << height);
295     const int bucketWidth = numberWidth(maxDelta, leftPadding);
296     int scalingFactor = 1;
297     // scale data if it exceeds maximum height
298     if (height > maxHeight) {
299         scalingFactor = (height + maxHeight) / maxHeight;
300         height /= scalingFactor;
301     }
302     body->appendFormat("\n%*s %3.2f %s", leftPadding + 11,
303             "Occurrences in", (elapsedMs / kMsPerSec), "seconds of audio:");
304     body->appendFormat("\n%*s%d, %lld, %lld\n", leftPadding + 11,
305             "Thread, hash, starting timestamp: ", author,
306             static_cast<long long int>(hash), static_cast<long long int>(startingTs));
307     // write histogram label line with bucket values
308     body->appendFormat("\n%s", " ");
309     body->appendFormat("%*s", leftPadding, " ");
310     for (auto const &x : buckets) {
311         const int colWidth = numberWidth(x.first, leftPadding);
312         body->appendFormat("%*d", colWidth, x.second);
313     }
314     // write histogram ascii art
315     body->appendFormat("\n%s", " ");
316     for (int row = height * scalingFactor; row >= 0; row -= scalingFactor) {
317         const int value = 1 << row;
318         body->appendFormat("%.*s", leftPadding, spaces.c_str());
319         for (auto const &x : buckets) {
320             const int colWidth = numberWidth(x.first, leftPadding);
321             body->appendFormat("%.*s%s", colWidth - 1,
322                                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) * bucketWidth, 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         const int colWidth = numberWidth(x.first, leftPadding);
336         body->appendFormat("%*.*f", colWidth, 1, x.first);
337     }
338     body->appendFormat("%.*s%s", bucketWidth, spaces.c_str(), "ms\n");
339 
340     // Now report glitches
341     body->appendFormat("\ntime elapsed between glitches and glitch timestamps:\n");
342     for (const auto &outlier: mOutlierData) {
343         body->appendFormat("%lld: %lld\n", static_cast<long long>(outlier.first),
344                            static_cast<long long>(outlier.second));
345     }
346 }
347 
348 //------------------------------------------------------------------------------
349 
350 // writes summary of performance into specified file descriptor
dump(int fd,int indent,PerformanceAnalysisMap & threadPerformanceAnalysis)351 void dump(int fd, int indent, PerformanceAnalysisMap &threadPerformanceAnalysis) {
352     String8 body;
353     const char* const kDirectory = "/data/misc/audioserver/";
354     for (auto & thread : threadPerformanceAnalysis) {
355         for (auto & hash: thread.second) {
356             PerformanceAnalysis& curr = hash.second;
357             // write performance data to console
358             curr.reportPerformance(&body, thread.first, hash.first);
359             if (!body.isEmpty()) {
360                 dumpLine(fd, indent, body);
361                 body.clear();
362             }
363             // write to file
364             writeToFile(curr.mHists, curr.mOutlierData, curr.mPeakTimestamps,
365                         kDirectory, false, thread.first, hash.first);
366         }
367     }
368 }
369 
370 
371 // Writes a string into specified file descriptor
dumpLine(int fd,int indent,const String8 & body)372 void dumpLine(int fd, int indent, const String8 &body) {
373     dprintf(fd, "%.*s%s \n", indent, "", body.string());
374 }
375 
376 } // namespace ReportPerformance
377 
378 }   // namespace android
379