xref: /aosp_15_r20/frameworks/av/media/libnblog/PerformanceAnalysis.cpp (revision ec779b8e0859a360c3d303172224686826e6e0e1)
1*ec779b8eSAndroid Build Coastguard Worker /*
2*ec779b8eSAndroid Build Coastguard Worker  * Copyright (C) 2017 The Android Open Source Project
3*ec779b8eSAndroid Build Coastguard Worker  *
4*ec779b8eSAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*ec779b8eSAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*ec779b8eSAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*ec779b8eSAndroid Build Coastguard Worker  *
8*ec779b8eSAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*ec779b8eSAndroid Build Coastguard Worker  *
10*ec779b8eSAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*ec779b8eSAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*ec779b8eSAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*ec779b8eSAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*ec779b8eSAndroid Build Coastguard Worker  * limitations under the License.
15*ec779b8eSAndroid Build Coastguard Worker  */
16*ec779b8eSAndroid Build Coastguard Worker 
17*ec779b8eSAndroid Build Coastguard Worker 
18*ec779b8eSAndroid Build Coastguard Worker #define LOG_TAG "PerformanceAnalysis"
19*ec779b8eSAndroid Build Coastguard Worker // #define LOG_NDEBUG 0
20*ec779b8eSAndroid Build Coastguard Worker // #define WRITE_TO_FILE
21*ec779b8eSAndroid Build Coastguard Worker 
22*ec779b8eSAndroid Build Coastguard Worker #include <algorithm>
23*ec779b8eSAndroid Build Coastguard Worker #include <climits>
24*ec779b8eSAndroid Build Coastguard Worker #include <deque>
25*ec779b8eSAndroid Build Coastguard Worker #include <iomanip>
26*ec779b8eSAndroid Build Coastguard Worker #include <math.h>
27*ec779b8eSAndroid Build Coastguard Worker #include <numeric>
28*ec779b8eSAndroid Build Coastguard Worker #include <sstream>
29*ec779b8eSAndroid Build Coastguard Worker #include <string>
30*ec779b8eSAndroid Build Coastguard Worker #include <vector>
31*ec779b8eSAndroid Build Coastguard Worker #include <stdarg.h>
32*ec779b8eSAndroid Build Coastguard Worker #include <stdint.h>
33*ec779b8eSAndroid Build Coastguard Worker #include <stdio.h>
34*ec779b8eSAndroid Build Coastguard Worker #include <string.h>
35*ec779b8eSAndroid Build Coastguard Worker #include <sys/prctl.h>
36*ec779b8eSAndroid Build Coastguard Worker #include <time.h>
37*ec779b8eSAndroid Build Coastguard Worker #include <new>
38*ec779b8eSAndroid Build Coastguard Worker #include <audio_utils/LogPlot.h>
39*ec779b8eSAndroid Build Coastguard Worker #include <audio_utils/roundup.h>
40*ec779b8eSAndroid Build Coastguard Worker #include <media/nblog/NBLog.h>
41*ec779b8eSAndroid Build Coastguard Worker #include <media/nblog/PerformanceAnalysis.h>
42*ec779b8eSAndroid Build Coastguard Worker #include <media/nblog/ReportPerformance.h>
43*ec779b8eSAndroid Build Coastguard Worker #include <utils/Log.h>
44*ec779b8eSAndroid Build Coastguard Worker #include <utils/String8.h>
45*ec779b8eSAndroid Build Coastguard Worker #include <utils/Timers.h>
46*ec779b8eSAndroid Build Coastguard Worker 
47*ec779b8eSAndroid Build Coastguard Worker #include <queue>
48*ec779b8eSAndroid Build Coastguard Worker #include <utility>
49*ec779b8eSAndroid Build Coastguard Worker 
50*ec779b8eSAndroid Build Coastguard Worker namespace android {
51*ec779b8eSAndroid Build Coastguard Worker namespace ReportPerformance {
52*ec779b8eSAndroid Build Coastguard Worker 
add(double value)53*ec779b8eSAndroid Build Coastguard Worker void Histogram::add(double value)
54*ec779b8eSAndroid Build Coastguard Worker {
55*ec779b8eSAndroid Build Coastguard Worker     if (mBinSize <= 0 || mBins.size() < 2) {
56*ec779b8eSAndroid Build Coastguard Worker         return;
57*ec779b8eSAndroid Build Coastguard Worker     }
58*ec779b8eSAndroid Build Coastguard Worker     // TODO Handle domain and range error exceptions?
59*ec779b8eSAndroid Build Coastguard Worker     const int unboundedIndex = lround((value - mLow) / mBinSize) + 1;
60*ec779b8eSAndroid Build Coastguard Worker     // std::clamp is introduced in C++17
61*ec779b8eSAndroid Build Coastguard Worker     //const int index = std::clamp(unboundedIndex, 0, (int)(mBins.size() - 1));
62*ec779b8eSAndroid Build Coastguard Worker     const int index = std::max(0, std::min((int)(mBins.size() - 1), unboundedIndex));
63*ec779b8eSAndroid Build Coastguard Worker     mBins[index]++;
64*ec779b8eSAndroid Build Coastguard Worker     mTotalCount++;
65*ec779b8eSAndroid Build Coastguard Worker }
66*ec779b8eSAndroid Build Coastguard Worker 
clear()67*ec779b8eSAndroid Build Coastguard Worker void Histogram::clear()
68*ec779b8eSAndroid Build Coastguard Worker {
69*ec779b8eSAndroid Build Coastguard Worker     std::fill(mBins.begin(), mBins.end(), 0);
70*ec779b8eSAndroid Build Coastguard Worker     mTotalCount = 0;
71*ec779b8eSAndroid Build Coastguard Worker }
72*ec779b8eSAndroid Build Coastguard Worker 
totalCount() const73*ec779b8eSAndroid Build Coastguard Worker uint64_t Histogram::totalCount() const
74*ec779b8eSAndroid Build Coastguard Worker {
75*ec779b8eSAndroid Build Coastguard Worker     return mTotalCount;
76*ec779b8eSAndroid Build Coastguard Worker }
77*ec779b8eSAndroid Build Coastguard Worker 
toString() const78*ec779b8eSAndroid Build Coastguard Worker std::string Histogram::toString() const {
79*ec779b8eSAndroid Build Coastguard Worker     std::stringstream ss;
80*ec779b8eSAndroid Build Coastguard Worker     static constexpr char kDivider = '|';
81*ec779b8eSAndroid Build Coastguard Worker     ss << kVersion << "," << mBinSize << "," << mNumBins << "," << mLow << ",{";
82*ec779b8eSAndroid Build Coastguard Worker     bool first = true;
83*ec779b8eSAndroid Build Coastguard Worker     for (size_t i = 0; i < mBins.size(); i++) {
84*ec779b8eSAndroid Build Coastguard Worker         if (mBins[i] != 0) {
85*ec779b8eSAndroid Build Coastguard Worker             if (!first) {
86*ec779b8eSAndroid Build Coastguard Worker                 ss << ",";
87*ec779b8eSAndroid Build Coastguard Worker             }
88*ec779b8eSAndroid Build Coastguard Worker             ss << static_cast<int>(i) - 1 << kDivider << mBins[i];
89*ec779b8eSAndroid Build Coastguard Worker             first = false;
90*ec779b8eSAndroid Build Coastguard Worker         }
91*ec779b8eSAndroid Build Coastguard Worker     }
92*ec779b8eSAndroid Build Coastguard Worker     ss << "}";
93*ec779b8eSAndroid Build Coastguard Worker 
94*ec779b8eSAndroid Build Coastguard Worker     return ss.str();
95*ec779b8eSAndroid Build Coastguard Worker }
96*ec779b8eSAndroid Build Coastguard Worker 
asciiArtString(size_t indent) const97*ec779b8eSAndroid Build Coastguard Worker std::string Histogram::asciiArtString(size_t indent) const {
98*ec779b8eSAndroid Build Coastguard Worker     if (totalCount() == 0 || mBinSize <= 0 || mBins.size() < 2) {
99*ec779b8eSAndroid Build Coastguard Worker         return "";
100*ec779b8eSAndroid Build Coastguard Worker     }
101*ec779b8eSAndroid Build Coastguard Worker 
102*ec779b8eSAndroid Build Coastguard Worker     static constexpr char kMarker = '-';
103*ec779b8eSAndroid Build Coastguard Worker     // One increment is considered one step of a bin's height.
104*ec779b8eSAndroid Build Coastguard Worker     static constexpr size_t kMarkersPerIncrement = 2;
105*ec779b8eSAndroid Build Coastguard Worker     static constexpr size_t kMaxIncrements = 64 + 1;
106*ec779b8eSAndroid Build Coastguard Worker     static constexpr size_t kMaxNumberWidth = 7;
107*ec779b8eSAndroid Build Coastguard Worker     static const std::string kMarkers(kMarkersPerIncrement * kMaxIncrements, kMarker);
108*ec779b8eSAndroid Build Coastguard Worker     static const std::string kSpaces(kMarkersPerIncrement * kMaxIncrements, ' ');
109*ec779b8eSAndroid Build Coastguard Worker     // get the last n characters of s, or the whole string if it is shorter
110*ec779b8eSAndroid Build Coastguard Worker     auto getTail = [](const size_t n, const std::string &s) {
111*ec779b8eSAndroid Build Coastguard Worker         return s.c_str() + s.size() - std::min(n, s.size());
112*ec779b8eSAndroid Build Coastguard Worker     };
113*ec779b8eSAndroid Build Coastguard Worker 
114*ec779b8eSAndroid Build Coastguard Worker     // Since totalCount() > 0, mBins is not empty and maxCount > 0.
115*ec779b8eSAndroid Build Coastguard Worker     const unsigned maxCount = *std::max_element(mBins.begin(), mBins.end());
116*ec779b8eSAndroid Build Coastguard Worker     const size_t maxIncrements = log2(maxCount) + 1;
117*ec779b8eSAndroid Build Coastguard Worker 
118*ec779b8eSAndroid Build Coastguard Worker     std::stringstream ss;
119*ec779b8eSAndroid Build Coastguard Worker 
120*ec779b8eSAndroid Build Coastguard Worker     // Non-zero bins must exist at this point because totalCount() > 0.
121*ec779b8eSAndroid Build Coastguard Worker     size_t firstNonZeroBin = 0;
122*ec779b8eSAndroid Build Coastguard Worker     // If firstNonZeroBin reaches mBins.size() - 1, then it must be a nonzero bin.
123*ec779b8eSAndroid Build Coastguard Worker     for (; firstNonZeroBin < mBins.size() - 1 && mBins[firstNonZeroBin] == 0; firstNonZeroBin++) {}
124*ec779b8eSAndroid Build Coastguard Worker     const size_t firstBinToPrint = firstNonZeroBin == 0 ? 0 : firstNonZeroBin - 1;
125*ec779b8eSAndroid Build Coastguard Worker 
126*ec779b8eSAndroid Build Coastguard Worker     size_t lastNonZeroBin = mBins.size() - 1;
127*ec779b8eSAndroid Build Coastguard Worker     // If lastNonZeroBin reaches 0, then it must be a nonzero bin.
128*ec779b8eSAndroid Build Coastguard Worker     for (; lastNonZeroBin > 0 && mBins[lastNonZeroBin] == 0; lastNonZeroBin--) {}
129*ec779b8eSAndroid Build Coastguard Worker     const size_t lastBinToPrint = lastNonZeroBin == mBins.size() - 1 ? lastNonZeroBin
130*ec779b8eSAndroid Build Coastguard Worker             : lastNonZeroBin + 1;
131*ec779b8eSAndroid Build Coastguard Worker 
132*ec779b8eSAndroid Build Coastguard Worker     for (size_t bin = firstBinToPrint; bin <= lastBinToPrint; bin++) {
133*ec779b8eSAndroid Build Coastguard Worker         ss << std::setw(indent + kMaxNumberWidth);
134*ec779b8eSAndroid Build Coastguard Worker         if (bin == 0) {
135*ec779b8eSAndroid Build Coastguard Worker             ss << "<";
136*ec779b8eSAndroid Build Coastguard Worker         } else if (bin == mBins.size() - 1) {
137*ec779b8eSAndroid Build Coastguard Worker             ss << ">";
138*ec779b8eSAndroid Build Coastguard Worker         } else {
139*ec779b8eSAndroid Build Coastguard Worker             ss << mLow + (bin - 1) * mBinSize;
140*ec779b8eSAndroid Build Coastguard Worker         }
141*ec779b8eSAndroid Build Coastguard Worker         ss << " |";
142*ec779b8eSAndroid Build Coastguard Worker         size_t increments = 0;
143*ec779b8eSAndroid Build Coastguard Worker         const uint64_t binCount = mBins[bin];
144*ec779b8eSAndroid Build Coastguard Worker         if (binCount > 0) {
145*ec779b8eSAndroid Build Coastguard Worker             increments = log2(binCount) + 1;
146*ec779b8eSAndroid Build Coastguard Worker             ss << getTail(increments * kMarkersPerIncrement, kMarkers);
147*ec779b8eSAndroid Build Coastguard Worker         }
148*ec779b8eSAndroid Build Coastguard Worker         ss << getTail((maxIncrements - increments + 1) * kMarkersPerIncrement, kSpaces)
149*ec779b8eSAndroid Build Coastguard Worker                 << binCount << "\n";
150*ec779b8eSAndroid Build Coastguard Worker     }
151*ec779b8eSAndroid Build Coastguard Worker     ss << "\n";
152*ec779b8eSAndroid Build Coastguard Worker 
153*ec779b8eSAndroid Build Coastguard Worker     return ss.str();
154*ec779b8eSAndroid Build Coastguard Worker }
155*ec779b8eSAndroid Build Coastguard Worker 
156*ec779b8eSAndroid Build Coastguard Worker //------------------------------------------------------------------------------
157*ec779b8eSAndroid Build Coastguard Worker 
158*ec779b8eSAndroid Build Coastguard Worker // Given an audio processing wakeup timestamp, buckets the time interval
159*ec779b8eSAndroid Build Coastguard Worker // since the previous timestamp into a histogram, searches for
160*ec779b8eSAndroid Build Coastguard Worker // outliers, analyzes the outlier series for unexpectedly
161*ec779b8eSAndroid Build Coastguard Worker // small or large values and stores these as peaks
logTsEntry(timestamp ts)162*ec779b8eSAndroid Build Coastguard Worker void PerformanceAnalysis::logTsEntry(timestamp ts) {
163*ec779b8eSAndroid Build Coastguard Worker     // after a state change, start a new series and do not
164*ec779b8eSAndroid Build Coastguard Worker     // record time intervals in-between
165*ec779b8eSAndroid Build Coastguard Worker     if (mBufferPeriod.mPrevTs == 0) {
166*ec779b8eSAndroid Build Coastguard Worker         mBufferPeriod.mPrevTs = ts;
167*ec779b8eSAndroid Build Coastguard Worker         return;
168*ec779b8eSAndroid Build Coastguard Worker     }
169*ec779b8eSAndroid Build Coastguard Worker 
170*ec779b8eSAndroid Build Coastguard Worker     // calculate time interval between current and previous timestamp
171*ec779b8eSAndroid Build Coastguard Worker     const msInterval diffMs = static_cast<msInterval>(
172*ec779b8eSAndroid Build Coastguard Worker         deltaMs(mBufferPeriod.mPrevTs, ts));
173*ec779b8eSAndroid Build Coastguard Worker 
174*ec779b8eSAndroid Build Coastguard Worker     const int diffJiffy = deltaJiffy(mBufferPeriod.mPrevTs, ts);
175*ec779b8eSAndroid Build Coastguard Worker 
176*ec779b8eSAndroid Build Coastguard Worker     // old versus new weight ratio when updating the buffer period mean
177*ec779b8eSAndroid Build Coastguard Worker     static constexpr double exponentialWeight = 0.999;
178*ec779b8eSAndroid Build Coastguard Worker     // update buffer period mean with exponential weighting
179*ec779b8eSAndroid Build Coastguard Worker     mBufferPeriod.mMean = (mBufferPeriod.mMean < 0) ? diffMs :
180*ec779b8eSAndroid Build Coastguard Worker             exponentialWeight * mBufferPeriod.mMean + (1.0 - exponentialWeight) * diffMs;
181*ec779b8eSAndroid Build Coastguard Worker     // set mOutlierFactor to a smaller value for the fastmixer thread
182*ec779b8eSAndroid Build Coastguard Worker     const int kFastMixerMax = 10;
183*ec779b8eSAndroid Build Coastguard Worker     // NormalMixer times vary much more than FastMixer times.
184*ec779b8eSAndroid Build Coastguard Worker     // TODO: mOutlierFactor values are set empirically based on what appears to be
185*ec779b8eSAndroid Build Coastguard Worker     // an outlier. Learn these values from the data.
186*ec779b8eSAndroid Build Coastguard Worker     mBufferPeriod.mOutlierFactor = mBufferPeriod.mMean < kFastMixerMax ? 1.8 : 2.0;
187*ec779b8eSAndroid Build Coastguard Worker     // set outlier threshold
188*ec779b8eSAndroid Build Coastguard Worker     mBufferPeriod.mOutlier = mBufferPeriod.mMean * mBufferPeriod.mOutlierFactor;
189*ec779b8eSAndroid Build Coastguard Worker 
190*ec779b8eSAndroid Build Coastguard Worker     // Check whether the time interval between the current timestamp
191*ec779b8eSAndroid Build Coastguard Worker     // and the previous one is long enough to count as an outlier
192*ec779b8eSAndroid Build Coastguard Worker     const bool isOutlier = detectAndStoreOutlier(diffMs);
193*ec779b8eSAndroid Build Coastguard Worker     // If an outlier was found, check whether it was a peak
194*ec779b8eSAndroid Build Coastguard Worker     if (isOutlier) {
195*ec779b8eSAndroid Build Coastguard Worker         /*bool isPeak =*/ detectAndStorePeak(
196*ec779b8eSAndroid Build Coastguard Worker             mOutlierData[0].first, mOutlierData[0].second);
197*ec779b8eSAndroid Build Coastguard Worker         // TODO: decide whether to insert a new empty histogram if a peak
198*ec779b8eSAndroid Build Coastguard Worker         // TODO: remove isPeak if unused to avoid "unused variable" error
199*ec779b8eSAndroid Build Coastguard Worker         // occurred at the current timestamp
200*ec779b8eSAndroid Build Coastguard Worker     }
201*ec779b8eSAndroid Build Coastguard Worker 
202*ec779b8eSAndroid Build Coastguard Worker     // Insert a histogram to mHists if it is empty, or
203*ec779b8eSAndroid Build Coastguard Worker     // close the current histogram and insert a new empty one if
204*ec779b8eSAndroid Build Coastguard Worker     // if the current histogram has spanned its maximum time interval.
205*ec779b8eSAndroid Build Coastguard Worker     if (mHists.empty() ||
206*ec779b8eSAndroid Build Coastguard Worker         deltaMs(mHists[0].first, ts) >= kMaxLength.HistTimespanMs) {
207*ec779b8eSAndroid Build Coastguard Worker         mHists.emplace_front(ts, std::map<int, int>());
208*ec779b8eSAndroid Build Coastguard Worker         // When memory is full, delete oldest histogram
209*ec779b8eSAndroid Build Coastguard Worker         // TODO: use a circular buffer
210*ec779b8eSAndroid Build Coastguard Worker         if (mHists.size() >= kMaxLength.Hists) {
211*ec779b8eSAndroid Build Coastguard Worker             mHists.resize(kMaxLength.Hists);
212*ec779b8eSAndroid Build Coastguard Worker         }
213*ec779b8eSAndroid Build Coastguard Worker     }
214*ec779b8eSAndroid Build Coastguard Worker     // add current time intervals to histogram
215*ec779b8eSAndroid Build Coastguard Worker     ++mHists[0].second[diffJiffy];
216*ec779b8eSAndroid Build Coastguard Worker     // update previous timestamp
217*ec779b8eSAndroid Build Coastguard Worker     mBufferPeriod.mPrevTs = ts;
218*ec779b8eSAndroid Build Coastguard Worker }
219*ec779b8eSAndroid Build Coastguard Worker 
220*ec779b8eSAndroid Build Coastguard Worker 
221*ec779b8eSAndroid Build Coastguard Worker // forces short-term histogram storage to avoid adding idle audio time interval
222*ec779b8eSAndroid Build Coastguard Worker // to buffer period data
handleStateChange()223*ec779b8eSAndroid Build Coastguard Worker void PerformanceAnalysis::handleStateChange() {
224*ec779b8eSAndroid Build Coastguard Worker     mBufferPeriod.mPrevTs = 0;
225*ec779b8eSAndroid Build Coastguard Worker     return;
226*ec779b8eSAndroid Build Coastguard Worker }
227*ec779b8eSAndroid Build Coastguard Worker 
228*ec779b8eSAndroid Build Coastguard Worker 
229*ec779b8eSAndroid Build Coastguard Worker // Checks whether the time interval between two outliers is far enough from
230*ec779b8eSAndroid Build Coastguard Worker // a typical delta to be considered a peak.
231*ec779b8eSAndroid Build Coastguard Worker // looks for changes in distribution (peaks), which can be either positive or negative.
232*ec779b8eSAndroid Build Coastguard Worker // The function sets the mean to the starting value and sigma to 0, and updates
233*ec779b8eSAndroid Build Coastguard Worker // them as long as no peak is detected. When a value is more than 'threshold'
234*ec779b8eSAndroid Build Coastguard Worker // standard deviations from the mean, a peak is detected and the mean and sigma
235*ec779b8eSAndroid Build Coastguard Worker // are set to the peak value and 0.
detectAndStorePeak(msInterval diff,timestamp ts)236*ec779b8eSAndroid Build Coastguard Worker bool PerformanceAnalysis::detectAndStorePeak(msInterval diff, timestamp ts) {
237*ec779b8eSAndroid Build Coastguard Worker     bool isPeak = false;
238*ec779b8eSAndroid Build Coastguard Worker     if (mOutlierData.empty()) {
239*ec779b8eSAndroid Build Coastguard Worker         return false;
240*ec779b8eSAndroid Build Coastguard Worker     }
241*ec779b8eSAndroid Build Coastguard Worker     // Update mean of the distribution
242*ec779b8eSAndroid Build Coastguard Worker     // TypicalDiff is used to check whether a value is unusually large
243*ec779b8eSAndroid Build Coastguard Worker     // when we cannot use standard deviations from the mean because the sd is set to 0.
244*ec779b8eSAndroid Build Coastguard Worker     mOutlierDistribution.mTypicalDiff = (mOutlierDistribution.mTypicalDiff *
245*ec779b8eSAndroid Build Coastguard Worker             (mOutlierData.size() - 1) + diff) / mOutlierData.size();
246*ec779b8eSAndroid Build Coastguard Worker 
247*ec779b8eSAndroid Build Coastguard Worker     // Initialize short-term mean at start of program
248*ec779b8eSAndroid Build Coastguard Worker     if (mOutlierDistribution.mMean == 0) {
249*ec779b8eSAndroid Build Coastguard Worker         mOutlierDistribution.mMean = diff;
250*ec779b8eSAndroid Build Coastguard Worker     }
251*ec779b8eSAndroid Build Coastguard Worker     // Update length of current sequence of outliers
252*ec779b8eSAndroid Build Coastguard Worker     mOutlierDistribution.mN++;
253*ec779b8eSAndroid Build Coastguard Worker 
254*ec779b8eSAndroid Build Coastguard Worker     // Check whether a large deviation from the mean occurred.
255*ec779b8eSAndroid Build Coastguard Worker     // If the standard deviation has been reset to zero, the comparison is
256*ec779b8eSAndroid Build Coastguard Worker     // instead to the mean of the full mOutlierInterval sequence.
257*ec779b8eSAndroid Build Coastguard Worker     if ((fabs(diff - mOutlierDistribution.mMean) <
258*ec779b8eSAndroid Build Coastguard Worker             mOutlierDistribution.kMaxDeviation * mOutlierDistribution.mSd) ||
259*ec779b8eSAndroid Build Coastguard Worker             (mOutlierDistribution.mSd == 0 &&
260*ec779b8eSAndroid Build Coastguard Worker             fabs(diff - mOutlierDistribution.mMean) <
261*ec779b8eSAndroid Build Coastguard Worker             mOutlierDistribution.mTypicalDiff)) {
262*ec779b8eSAndroid Build Coastguard Worker         // update the mean and sd using online algorithm
263*ec779b8eSAndroid Build Coastguard Worker         // https://en.wikipedia.org/wiki/
264*ec779b8eSAndroid Build Coastguard Worker         // Algorithms_for_calculating_variance#Online_algorithm
265*ec779b8eSAndroid Build Coastguard Worker         mOutlierDistribution.mN++;
266*ec779b8eSAndroid Build Coastguard Worker         const double kDelta = diff - mOutlierDistribution.mMean;
267*ec779b8eSAndroid Build Coastguard Worker         mOutlierDistribution.mMean += kDelta / mOutlierDistribution.mN;
268*ec779b8eSAndroid Build Coastguard Worker         const double kDelta2 = diff - mOutlierDistribution.mMean;
269*ec779b8eSAndroid Build Coastguard Worker         mOutlierDistribution.mM2 += kDelta * kDelta2;
270*ec779b8eSAndroid Build Coastguard Worker         mOutlierDistribution.mSd = (mOutlierDistribution.mN < 2) ? 0 :
271*ec779b8eSAndroid Build Coastguard Worker                 sqrt(mOutlierDistribution.mM2 / (mOutlierDistribution.mN - 1));
272*ec779b8eSAndroid Build Coastguard Worker     } else {
273*ec779b8eSAndroid Build Coastguard Worker         // new value is far from the mean:
274*ec779b8eSAndroid Build Coastguard Worker         // store peak timestamp and reset mean, sd, and short-term sequence
275*ec779b8eSAndroid Build Coastguard Worker         isPeak = true;
276*ec779b8eSAndroid Build Coastguard Worker         mPeakTimestamps.emplace_front(ts);
277*ec779b8eSAndroid Build Coastguard Worker         // if mPeaks has reached capacity, delete oldest data
278*ec779b8eSAndroid Build Coastguard Worker         // Note: this means that mOutlierDistribution values do not exactly
279*ec779b8eSAndroid Build Coastguard Worker         // match the data we have in mPeakTimestamps, but this is not an issue
280*ec779b8eSAndroid Build Coastguard Worker         // in practice for estimating future peaks.
281*ec779b8eSAndroid Build Coastguard Worker         // TODO: turn this into a circular buffer
282*ec779b8eSAndroid Build Coastguard Worker         if (mPeakTimestamps.size() >= kMaxLength.Peaks) {
283*ec779b8eSAndroid Build Coastguard Worker             mPeakTimestamps.resize(kMaxLength.Peaks);
284*ec779b8eSAndroid Build Coastguard Worker         }
285*ec779b8eSAndroid Build Coastguard Worker         mOutlierDistribution.mMean = 0;
286*ec779b8eSAndroid Build Coastguard Worker         mOutlierDistribution.mSd = 0;
287*ec779b8eSAndroid Build Coastguard Worker         mOutlierDistribution.mN = 0;
288*ec779b8eSAndroid Build Coastguard Worker         mOutlierDistribution.mM2 = 0;
289*ec779b8eSAndroid Build Coastguard Worker     }
290*ec779b8eSAndroid Build Coastguard Worker     return isPeak;
291*ec779b8eSAndroid Build Coastguard Worker }
292*ec779b8eSAndroid Build Coastguard Worker 
293*ec779b8eSAndroid Build Coastguard Worker 
294*ec779b8eSAndroid Build Coastguard Worker // Determines whether the difference between a timestamp and the previous
295*ec779b8eSAndroid Build Coastguard Worker // one is beyond a threshold. If yes, stores the timestamp as an outlier
296*ec779b8eSAndroid Build Coastguard Worker // and writes to mOutlierdata in the following format:
297*ec779b8eSAndroid Build Coastguard Worker // Time elapsed since previous outlier: Timestamp of start of outlier
298*ec779b8eSAndroid Build Coastguard Worker // e.g. timestamps (ms) 1, 4, 5, 16, 18, 28 will produce pairs (4, 5), (13, 18).
299*ec779b8eSAndroid Build Coastguard Worker // TODO: learn what timestamp sequences correlate with glitches instead of
300*ec779b8eSAndroid Build Coastguard Worker // manually designing a heuristic.
detectAndStoreOutlier(const msInterval diffMs)301*ec779b8eSAndroid Build Coastguard Worker bool PerformanceAnalysis::detectAndStoreOutlier(const msInterval diffMs) {
302*ec779b8eSAndroid Build Coastguard Worker     bool isOutlier = false;
303*ec779b8eSAndroid Build Coastguard Worker     if (diffMs >= mBufferPeriod.mOutlier) {
304*ec779b8eSAndroid Build Coastguard Worker         isOutlier = true;
305*ec779b8eSAndroid Build Coastguard Worker         mOutlierData.emplace_front(
306*ec779b8eSAndroid Build Coastguard Worker                 mOutlierDistribution.mElapsed, mBufferPeriod.mPrevTs);
307*ec779b8eSAndroid Build Coastguard Worker         // Remove oldest value if the vector is full
308*ec779b8eSAndroid Build Coastguard Worker         // TODO: turn this into a circular buffer
309*ec779b8eSAndroid Build Coastguard Worker         // TODO: make sure kShortHistSize is large enough that that data will never be lost
310*ec779b8eSAndroid Build Coastguard Worker         // before being written to file or to a FIFO
311*ec779b8eSAndroid Build Coastguard Worker         if (mOutlierData.size() >= kMaxLength.Outliers) {
312*ec779b8eSAndroid Build Coastguard Worker             mOutlierData.resize(kMaxLength.Outliers);
313*ec779b8eSAndroid Build Coastguard Worker         }
314*ec779b8eSAndroid Build Coastguard Worker         mOutlierDistribution.mElapsed = 0;
315*ec779b8eSAndroid Build Coastguard Worker     }
316*ec779b8eSAndroid Build Coastguard Worker     mOutlierDistribution.mElapsed += diffMs;
317*ec779b8eSAndroid Build Coastguard Worker     return isOutlier;
318*ec779b8eSAndroid Build Coastguard Worker }
319*ec779b8eSAndroid Build Coastguard Worker 
320*ec779b8eSAndroid Build Coastguard Worker // rounds value to precision based on log-distance from mean
321*ec779b8eSAndroid Build Coastguard Worker __attribute__((no_sanitize("signed-integer-overflow")))
logRound(double x,double mean)322*ec779b8eSAndroid Build Coastguard Worker inline double logRound(double x, double mean) {
323*ec779b8eSAndroid Build Coastguard Worker     // Larger values decrease range of high resolution and prevent overflow
324*ec779b8eSAndroid Build Coastguard Worker     // of a histogram on the console.
325*ec779b8eSAndroid Build Coastguard Worker     // The following formula adjusts kBase based on the buffer period length.
326*ec779b8eSAndroid Build Coastguard Worker     // Different threads have buffer periods ranging from 2 to 40. The
327*ec779b8eSAndroid Build Coastguard Worker     // formula below maps buffer period 2 to kBase = ~1, 4 to ~2, 20 to ~3, 40 to ~4.
328*ec779b8eSAndroid Build Coastguard Worker     // TODO: tighten this for higher means, the data still overflows
329*ec779b8eSAndroid Build Coastguard Worker     const double kBase = log(mean) / log(2.2);
330*ec779b8eSAndroid Build Coastguard Worker     const double power = floor(
331*ec779b8eSAndroid Build Coastguard Worker         log(abs(x - mean) / mean) / log(kBase)) + 2;
332*ec779b8eSAndroid Build Coastguard Worker     // do not round values close to the mean
333*ec779b8eSAndroid Build Coastguard Worker     if (power < 1) {
334*ec779b8eSAndroid Build Coastguard Worker         return x;
335*ec779b8eSAndroid Build Coastguard Worker     }
336*ec779b8eSAndroid Build Coastguard Worker     const int factor = static_cast<int>(pow(10, power));
337*ec779b8eSAndroid Build Coastguard Worker     return (static_cast<int>(x) * factor) / factor;
338*ec779b8eSAndroid Build Coastguard Worker }
339*ec779b8eSAndroid Build Coastguard Worker 
340*ec779b8eSAndroid Build Coastguard Worker // TODO Make it return a std::string instead of modifying body
341*ec779b8eSAndroid Build Coastguard Worker // TODO: move this to ReportPerformance, probably make it a friend function
342*ec779b8eSAndroid Build Coastguard Worker // of PerformanceAnalysis
reportPerformance(String8 * body,int author,log_hash_t hash,int maxHeight)343*ec779b8eSAndroid Build Coastguard Worker void PerformanceAnalysis::reportPerformance(String8 *body, int author, log_hash_t hash,
344*ec779b8eSAndroid Build Coastguard Worker                                             int maxHeight) {
345*ec779b8eSAndroid Build Coastguard Worker     if (mHists.empty() || body == nullptr) {
346*ec779b8eSAndroid Build Coastguard Worker         return;
347*ec779b8eSAndroid Build Coastguard Worker     }
348*ec779b8eSAndroid Build Coastguard Worker 
349*ec779b8eSAndroid Build Coastguard Worker     // ms of active audio in displayed histogram
350*ec779b8eSAndroid Build Coastguard Worker     double elapsedMs = 0;
351*ec779b8eSAndroid Build Coastguard Worker     // starting timestamp of histogram
352*ec779b8eSAndroid Build Coastguard Worker     timestamp startingTs = mHists[0].first;
353*ec779b8eSAndroid Build Coastguard Worker 
354*ec779b8eSAndroid Build Coastguard Worker     // histogram which stores .1 precision ms counts instead of Jiffy multiple counts
355*ec779b8eSAndroid Build Coastguard Worker     std::map<double, int> buckets;
356*ec779b8eSAndroid Build Coastguard Worker     for (const auto &shortHist: mHists) {
357*ec779b8eSAndroid Build Coastguard Worker         for (const auto &countPair : shortHist.second) {
358*ec779b8eSAndroid Build Coastguard Worker             const double ms = static_cast<double>(countPair.first) / kJiffyPerMs;
359*ec779b8eSAndroid Build Coastguard Worker             buckets[logRound(ms, mBufferPeriod.mMean)] += countPair.second;
360*ec779b8eSAndroid Build Coastguard Worker             elapsedMs += ms * countPair.second;
361*ec779b8eSAndroid Build Coastguard Worker         }
362*ec779b8eSAndroid Build Coastguard Worker     }
363*ec779b8eSAndroid Build Coastguard Worker 
364*ec779b8eSAndroid Build Coastguard Worker     static const int SIZE = 128;
365*ec779b8eSAndroid Build Coastguard Worker     char title[SIZE];
366*ec779b8eSAndroid Build Coastguard Worker     snprintf(title, sizeof(title), "\n%s %3.2f %s\n%s%d, %lld, %lld\n",
367*ec779b8eSAndroid Build Coastguard Worker             "Occurrences in", (elapsedMs / kMsPerSec), "seconds of audio:",
368*ec779b8eSAndroid Build Coastguard Worker             "Thread, hash, starting timestamp: ", author,
369*ec779b8eSAndroid Build Coastguard Worker             static_cast<long long>(hash), static_cast<long long>(startingTs));
370*ec779b8eSAndroid Build Coastguard Worker     static const char * const kLabel = "ms";
371*ec779b8eSAndroid Build Coastguard Worker 
372*ec779b8eSAndroid Build Coastguard Worker     body->appendFormat("%s",
373*ec779b8eSAndroid Build Coastguard Worker             audio_utils_plot_histogram(buckets, title, kLabel, maxHeight).c_str());
374*ec779b8eSAndroid Build Coastguard Worker 
375*ec779b8eSAndroid Build Coastguard Worker     // Now report glitches
376*ec779b8eSAndroid Build Coastguard Worker     body->appendFormat("\ntime elapsed between glitches and glitch timestamps:\n");
377*ec779b8eSAndroid Build Coastguard Worker     for (const auto &outlier: mOutlierData) {
378*ec779b8eSAndroid Build Coastguard Worker         body->appendFormat("%lld: %lld\n", static_cast<long long>(outlier.first),
379*ec779b8eSAndroid Build Coastguard Worker                            static_cast<long long>(outlier.second));
380*ec779b8eSAndroid Build Coastguard Worker     }
381*ec779b8eSAndroid Build Coastguard Worker }
382*ec779b8eSAndroid Build Coastguard Worker 
383*ec779b8eSAndroid Build Coastguard Worker //------------------------------------------------------------------------------
384*ec779b8eSAndroid Build Coastguard Worker 
385*ec779b8eSAndroid Build Coastguard Worker // writes summary of performance into specified file descriptor
dump(int fd,int indent,PerformanceAnalysisMap & threadPerformanceAnalysis)386*ec779b8eSAndroid Build Coastguard Worker void dump(int fd, int indent, PerformanceAnalysisMap &threadPerformanceAnalysis) {
387*ec779b8eSAndroid Build Coastguard Worker     String8 body;
388*ec779b8eSAndroid Build Coastguard Worker #ifdef WRITE_TO_FILE
389*ec779b8eSAndroid Build Coastguard Worker     const char* const kDirectory = "/data/misc/audioserver/";
390*ec779b8eSAndroid Build Coastguard Worker #endif
391*ec779b8eSAndroid Build Coastguard Worker     for (auto & thread : threadPerformanceAnalysis) {
392*ec779b8eSAndroid Build Coastguard Worker         for (auto & hash: thread.second) {
393*ec779b8eSAndroid Build Coastguard Worker             PerformanceAnalysis& curr = hash.second;
394*ec779b8eSAndroid Build Coastguard Worker             // write performance data to console
395*ec779b8eSAndroid Build Coastguard Worker             curr.reportPerformance(&body, thread.first, hash.first);
396*ec779b8eSAndroid Build Coastguard Worker             if (!body.empty()) {
397*ec779b8eSAndroid Build Coastguard Worker                 dumpLine(fd, indent, body);
398*ec779b8eSAndroid Build Coastguard Worker                 body.clear();
399*ec779b8eSAndroid Build Coastguard Worker             }
400*ec779b8eSAndroid Build Coastguard Worker #ifdef WRITE_TO_FILE
401*ec779b8eSAndroid Build Coastguard Worker             // write to file. Enable by uncommenting macro at top of file.
402*ec779b8eSAndroid Build Coastguard Worker             writeToFile(curr.mHists, curr.mOutlierData, curr.mPeakTimestamps,
403*ec779b8eSAndroid Build Coastguard Worker                         kDirectory, false, thread.first, hash.first);
404*ec779b8eSAndroid Build Coastguard Worker #endif
405*ec779b8eSAndroid Build Coastguard Worker         }
406*ec779b8eSAndroid Build Coastguard Worker     }
407*ec779b8eSAndroid Build Coastguard Worker }
408*ec779b8eSAndroid Build Coastguard Worker 
409*ec779b8eSAndroid Build Coastguard Worker 
410*ec779b8eSAndroid Build Coastguard Worker // Writes a string into specified file descriptor
dumpLine(int fd,int indent,const String8 & body)411*ec779b8eSAndroid Build Coastguard Worker void dumpLine(int fd, int indent, const String8 &body) {
412*ec779b8eSAndroid Build Coastguard Worker     dprintf(fd, "%.*s%s \n", indent, "", body.c_str());
413*ec779b8eSAndroid Build Coastguard Worker }
414*ec779b8eSAndroid Build Coastguard Worker 
415*ec779b8eSAndroid Build Coastguard Worker } // namespace ReportPerformance
416*ec779b8eSAndroid Build Coastguard Worker }   // namespace android
417