blob: e9212e62d4a30fb183aaae68426bf9ad45f1b375 [file] [log] [blame]
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -07001/*
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>
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -070024#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/nbaio/NBLog.h>
37#include <media/nbaio/PerformanceAnalysis.h>
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070038#include <media/nbaio/ReportPerformance.h>
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -070039#include <utils/Log.h>
40#include <utils/String8.h>
41
42#include <queue>
43#include <utility>
44
45namespace android {
46
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070047namespace ReportPerformance {
48
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -070049PerformanceAnalysis::PerformanceAnalysis() {
50 // These variables will be (FIXME) learned from the data
51 kPeriodMs = 4; // typical buffer period (mode)
52 // average number of Ms spent processing buffer
53 kPeriodMsCPU = static_cast<int>(kPeriodMs * kRatio);
54}
55
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -070056static int widthOf(int x) {
57 int width = 0;
58 while (x > 0) {
59 ++width;
60 x /= 10;
61 }
62 return width;
63}
64
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -070065// Given a series of audio processing wakeup timestamps,
66// buckets the time intervals into a histogram, searches for
67// outliers, analyzes the outlier series for unexpectedly
68// small or large values and stores these as peaks, and flushes
69// the timestamp series from memory.
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070070void PerformanceAnalysis::processAndFlushTimeStampSeries() {
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -070071 if (mTimeStampSeries.empty()) {
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -070072 return;
73 }
74
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -070075 // TODO: add mPrev ts depending on whether or not handleStateChange was called
76 for (size_t i = 1; i < mTimeStampSeries.size(); i++) {
77 // Check whether the current timestamp is an outlier
78 const bool isOutlier = detectAndStoreOutlier(mTimeStampSeries[i]);
79 // If an outlier was found, check whether it was a peak
80 if (isOutlier) {
81 /*bool isPeak =*/ detectAndStorePeak(
82 mOutlierData[0].first, mOutlierData[0].second);
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -070083 }
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -070084 // Insert a histogram to mHists if it is empty, or
85 // close the current histogram and insert a new empty one if
86 // if the current histogram has spanned its maximum time interval.
87 // Also insert a new empty histogram if a change in behavior (peak)
88 // occurred at the current timestamp
89 if (mHists.empty() || deltaMs(mHists[0].first, mTimeStampSeries[i]) >=
90 kMaxLength.HistTimespanMs) {
91 mHists.emplace_front(static_cast<uint64_t>(mTimeStampSeries[i]),
92 std::map<int, int>());
93 // When memory is full, delete oldest histogram
94 // TODO: use a circular buffer
95 if (mHists.size() >= kMaxLength.Hists) {
96 mHists.resize(kMaxLength.Hists);
97 }
98 }
99 // add current time intervals to histogram
100 ++mHists[0].second[deltaMs(mTimeStampSeries[i-1], mTimeStampSeries[i])];
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -0700101 }
102
103 // clear the timestamps
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700104 mTimeStampSeries.clear();
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700105 // reset outlier values
106 mOutlierDistribution.mPrevNs = -1;
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -0700107}
108
109// forces short-term histogram storage to avoid adding idle audio time interval
110// to buffer period data
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700111void PerformanceAnalysis::handleStateChange() {
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -0700112 ALOGD("handleStateChange");
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700113 processAndFlushTimeStampSeries();
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -0700114 return;
115}
116
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700117// Takes a single buffer period timestamp entry information and stores it in a
118// temporary series of timestamps. Once the series is full, the data is analyzed,
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700119// stored, and emptied.
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700120void PerformanceAnalysis::logTsEntry(int64_t ts) {
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700121 // TODO might want to filter excessively high outliers, which are usually caused
122 // by the thread being inactive.
123 // Store time series data for each reader in order to bucket it once there
124 // is enough data. Then, write to recentHists as a histogram.
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700125 mTimeStampSeries.push_back(ts);
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -0700126 // if length of the time series has reached kShortHistSize samples,
127 // analyze the data and flush the timestamp series from memory
Sanna Catherine de Treville Wager716d2c62017-07-25 13:46:36 -0700128 if (mTimeStampSeries.size() >= kMaxLength.TimeStamps) {
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700129 processAndFlushTimeStampSeries();
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700130 }
131}
132
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700133// Checks whether the time interval between two outliers is far enough from
134// a typical delta to be considered a peak.
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700135// looks for changes in distribution (peaks), which can be either positive or negative.
136// The function sets the mean to the starting value and sigma to 0, and updates
137// them as long as no peak is detected. When a value is more than 'threshold'
138// standard deviations from the mean, a peak is detected and the mean and sigma
139// are set to the peak value and 0.
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700140bool PerformanceAnalysis::detectAndStorePeak(outlierInterval diff, timestamp ts) {
141 bool isPeak = false;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700142 if (mOutlierData.empty()) {
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700143 ALOGD("mOutlierData is empty");
144 return false;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700145 }
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700146 // Update mean of the distribution
147 // TypicalDiff is used to check whether a value is unusually large
148 // when we cannot use standard deviations from the mean because the sd is set to 0.
149 mOutlierDistribution.mTypicalDiff = (mOutlierDistribution.mTypicalDiff *
150 (mOutlierData.size() - 1) + diff) / mOutlierData.size();
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700151
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700152 // Initialize short-term mean at start of program
153 if (mOutlierDistribution.mMean == 0) {
154 mOutlierDistribution.mMean = static_cast<double>(diff);
155 }
156 // Update length of current sequence of outliers
157 mOutlierDistribution.mN++;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700158
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700159 // If statement checks whether a large deviation from the mean occurred.
160 // If the standard deviation has been reset to zero, the comparison is
161 // instead to the mean of the full mOutlierInterval sequence.
162 if ((fabs(static_cast<double>(diff) - mOutlierDistribution.mMean) <
163 mOutlierDistribution.kMaxDeviation * mOutlierDistribution.mSd) ||
164 (mOutlierDistribution.mSd == 0 &&
165 fabs(diff - mOutlierDistribution.mMean) <
166 mOutlierDistribution.mTypicalDiff)) {
167 // update the mean and sd using online algorithm
168 // https://en.wikipedia.org/wiki/
169 // Algorithms_for_calculating_variance#Online_algorithm
170 mOutlierDistribution.mN++;
171 const double kDelta = diff - mOutlierDistribution.mMean;
172 mOutlierDistribution.mMean += kDelta / mOutlierDistribution.mN;
173 const double kDelta2 = diff - mOutlierDistribution.mMean;
174 mOutlierDistribution.mM2 += kDelta * kDelta2;
175 mOutlierDistribution.mSd = (mOutlierDistribution.mN < 2) ? 0 :
176 mOutlierDistribution.mM2 / (mOutlierDistribution.mN - 1);
177 } else {
178 // new value is far from the mean:
179 // store peak timestamp and reset mean, sd, and short-term sequence
180 isPeak = true;
181 mPeakTimestamps.emplace_front(ts);
182 // if mPeaks has reached capacity, delete oldest data
183 // Note: this means that mOutlierDistribution values do not exactly
184 // match the data we have in mPeakTimestamps, but this is not an issue
185 // in practice for estimating future peaks.
186 // TODO: turn this into a circular buffer
187 if (mPeakTimestamps.size() >= kMaxLength.Peaks) {
188 mPeakTimestamps.resize(kMaxLength.Peaks);
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700189 }
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700190 mOutlierDistribution.mMean = 0;
191 mOutlierDistribution.mSd = 0;
192 mOutlierDistribution.mN = 0;
193 mOutlierDistribution.mM2 = 0;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700194 }
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700195 ALOGD("outlier distr %f %f", mOutlierDistribution.mMean, mOutlierDistribution.mSd);
196 return isPeak;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700197}
198
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700199// Determines whether the difference between a timestamp and the previous
200// one is beyond a threshold. If yes, stores the timestamp as an outlier
201// and writes to mOutlierdata in the following format:
202// Time elapsed since previous outlier: Timestamp of start of outlier
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700203// e.g. timestamps (ms) 1, 4, 5, 16, 18, 28 will produce pairs (4, 5), (13, 18).
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700204bool PerformanceAnalysis::detectAndStoreOutlier(const int64_t ts) {
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700205 // first pass: need to initialize
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700206 if (mOutlierDistribution.mPrevNs == -1) {
207 mOutlierDistribution.mPrevNs = ts;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700208 }
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700209 bool isOutlier = false;
210 const uint64_t diffMs = static_cast<uint64_t>(deltaMs(mOutlierDistribution.mPrevNs, ts));
211 // ALOGD("%d\t", static_cast<int>(diffMs));
212 if (diffMs >= static_cast<uint64_t>(kOutlierMs)) {
213 isOutlier = true;
214 //ALOGD("outlier outlier %d %d", static_cast<int>(diffMs),
215 // static_cast<int>(mOutlierDistribution.mElapsed));
216 mOutlierData.emplace_front(mOutlierDistribution.mElapsed,
217 static_cast<uint64_t>(mOutlierDistribution.mPrevNs));
218 // Remove oldest value if the vector is full
219 // TODO: turn this into a circular buffer
220 // TODO: make sure kShortHistSize is large enough that that data will never be lost
221 // before being written to file or to a FIFO
222 if (mOutlierData.size() >= kMaxLength.Outliers) {
223 mOutlierData.resize(kMaxLength.Outliers);
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700224 }
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700225 mOutlierDistribution.mElapsed = 0;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700226 }
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700227 mOutlierDistribution.mElapsed += diffMs;
228 mOutlierDistribution.mPrevNs = ts;
229 return isOutlier;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700230}
231
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700232// TODO Make it return a std::string instead of modifying body --> is this still relevant?
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700233// TODO consider changing all ints to uint32_t or uint64_t
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700234// TODO: move this to ReportPerformance, probably make it a friend function of PerformanceAnalysis
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700235void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -0700236 if (mHists.empty()) {
237 ALOGD("reportPerformance: mHists is empty");
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700238 return;
239 }
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -0700240 ALOGD("reportPerformance: hists size %d", static_cast<int>(mHists.size()));
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700241
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700242 std::map<int, int> buckets;
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -0700243 for (const auto &shortHist: mHists) {
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700244 for (const auto &countPair : shortHist.second) {
245 buckets[countPair.first] += countPair.second;
246 }
247 }
248
249 // underscores and spaces length corresponds to maximum width of histogram
250 static const int kLen = 40;
251 std::string underscores(kLen, '_');
252 std::string spaces(kLen, ' ');
253
254 auto it = buckets.begin();
255 int maxDelta = it->first;
256 int maxCount = it->second;
257 // Compute maximum values
258 while (++it != buckets.end()) {
259 if (it->first > maxDelta) {
260 maxDelta = it->first;
261 }
262 if (it->second > maxCount) {
263 maxCount = it->second;
264 }
265 }
266 int height = log2(maxCount) + 1; // maxCount > 0, safe to call log2
267 const int leftPadding = widthOf(1 << height);
268 const int colWidth = std::max(std::max(widthOf(maxDelta) + 1, 3), leftPadding + 2);
269 int scalingFactor = 1;
270 // scale data if it exceeds maximum height
271 if (height > maxHeight) {
272 scalingFactor = (height + maxHeight) / maxHeight;
273 height /= scalingFactor;
274 }
Sanna Catherine de Treville Wagere4865262017-07-14 16:24:15 -0700275 // TODO: print reader (author) ID
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700276 body->appendFormat("\n%*s", leftPadding + 11, "Occurrences");
277 // write histogram label line with bucket values
278 body->appendFormat("\n%s", " ");
279 body->appendFormat("%*s", leftPadding, " ");
280 for (auto const &x : buckets) {
281 body->appendFormat("%*d", colWidth, x.second);
282 }
283 // write histogram ascii art
284 body->appendFormat("\n%s", " ");
285 for (int row = height * scalingFactor; row >= 0; row -= scalingFactor) {
286 const int value = 1 << row;
287 body->appendFormat("%.*s", leftPadding, spaces.c_str());
288 for (auto const &x : buckets) {
Sanna Catherine de Treville Wager716d2c62017-07-25 13:46:36 -0700289 body->appendFormat("%.*s%s", colWidth - 1,
290 spaces.c_str(), x.second < value ? " " : "|");
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700291 }
292 body->appendFormat("\n%s", " ");
293 }
294 // print x-axis
295 const int columns = static_cast<int>(buckets.size());
296 body->appendFormat("%*c", leftPadding, ' ');
297 body->appendFormat("%.*s", (columns + 1) * colWidth, underscores.c_str());
298 body->appendFormat("\n%s", " ");
299
300 // write footer with bucket labels
301 body->appendFormat("%*s", leftPadding, " ");
302 for (auto const &x : buckets) {
303 body->appendFormat("%*d", colWidth, x.first);
304 }
305 body->appendFormat("%.*s%s", colWidth, spaces.c_str(), "ms\n");
306
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700307 // Now report glitches
308 body->appendFormat("\ntime elapsed between glitches and glitch timestamps\n");
309 for (const auto &outlier: mOutlierData) {
310 body->appendFormat("%lld: %lld\n", static_cast<long long>(outlier.first),
311 static_cast<long long>(outlier.second));
312 }
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700313}
314
Sanna Catherine de Treville Wagerf8c34282017-07-25 11:31:18 -0700315// TODO: decide whether to use this or whether it is overkill, and it is enough
316// to only treat as glitches single wakeup call intervals which are too long.
317// Ultimately, glitch detection will be directly on the audio signal.
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700318// Produces a log warning if the timing of recent buffer periods caused a glitch
319// Computes sum of running window of three buffer periods
320// Checks whether the buffer periods leave enough CPU time for the next one
321// e.g. if a buffer period is expected to be 4 ms and a buffer requires 3 ms of CPU time,
322// here are some glitch cases:
323// 4 + 4 + 6 ; 5 + 4 + 5; 2 + 2 + 10
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700324void PerformanceAnalysis::alertIfGlitch(const std::vector<int64_t> &samples) {
325 std::deque<int> periods(kNumBuff, kPeriodMs);
326 for (size_t i = 2; i < samples.size(); ++i) { // skip first time entry
327 periods.push_front(deltaMs(samples[i - 1], samples[i]));
328 periods.pop_back();
329 // TODO: check that all glitch cases are covered
330 if (std::accumulate(periods.begin(), periods.end(), 0) > kNumBuff * kPeriodMs +
331 kPeriodMs - kPeriodMsCPU) {
332 ALOGW("A glitch occurred");
333 periods.assign(kNumBuff, kPeriodMs);
334 }
335 }
336 return;
337}
338
Sanna Catherine de Treville Wagercf6c75a2017-07-21 17:05:25 -0700339//------------------------------------------------------------------------------
340
341// writes summary of performance into specified file descriptor
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700342void dump(int fd, int indent, PerformanceAnalysisMap &threadPerformanceAnalysis) {
Sanna Catherine de Treville Wagercf6c75a2017-07-21 17:05:25 -0700343 String8 body;
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700344 int i = 0; // TODO: delete
Sanna Catherine de Treville Wagerf8c34282017-07-25 11:31:18 -0700345 const char* const kDirectory = "/data/misc/audioserver/";
Sanna Catherine de Treville Wagercf6c75a2017-07-21 17:05:25 -0700346 for (auto & thread : threadPerformanceAnalysis) {
Sanna Catherine de Treville Wagerd0965172017-07-24 13:42:44 -0700347 for (auto & hash: thread.second) {
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700348 ALOGD("hash number %d", i++);
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700349 PerformanceAnalysis& curr = hash.second;
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700350 // Add any new data
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700351 curr.processAndFlushTimeStampSeries();
352 // write performance data to console
353 curr.reportPerformance(&body);
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700354 if (!body.isEmpty()) {
355 dumpLine(fd, indent, body);
356 body.clear();
357 }
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700358 // write to file
Sanna Catherine de Treville Wagerf8c34282017-07-25 11:31:18 -0700359 writeToFile(curr.mHists, curr.mOutlierData, curr.mPeakTimestamps,
360 kDirectory, false, thread.first, hash.first);
Sanna Catherine de Treville Wagerd0965172017-07-24 13:42:44 -0700361 }
Sanna Catherine de Treville Wagercf6c75a2017-07-21 17:05:25 -0700362 }
Sanna Catherine de Treville Wagercf6c75a2017-07-21 17:05:25 -0700363}
364
365// Writes a string into specified file descriptor
366void dumpLine(int fd, int indent, const String8 &body) {
367 dprintf(fd, "%.*s%s \n", indent, "", body.string());
368}
369
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700370} // namespace ReportPerformance
371
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700372} // namespace android