blob: fb3bddc3f61ebf732ee75d8971cd7c47e6854338 [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/CallStack.h> // used to print callstack
40#include <utils/Log.h>
41#include <utils/String8.h>
42
43#include <queue>
44#include <utility>
45
46namespace android {
47
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070048namespace ReportPerformance {
49
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -070050PerformanceAnalysis::PerformanceAnalysis() {
51 // These variables will be (FIXME) learned from the data
52 kPeriodMs = 4; // typical buffer period (mode)
53 // average number of Ms spent processing buffer
54 kPeriodMsCPU = static_cast<int>(kPeriodMs * kRatio);
55}
56
57// converts a time series into a map. key: buffer period length. value: count
58static std::map<int, int> buildBuckets(const std::vector<int64_t> &samples) {
59 // TODO allow buckets of variable resolution
60 std::map<int, int> buckets;
61 for (size_t i = 1; i < samples.size(); ++i) {
62 ++buckets[deltaMs(samples[i - 1], samples[i])];
63 }
64 return buckets;
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -070065}
66
67static int widthOf(int x) {
68 int width = 0;
69 while (x > 0) {
70 ++width;
71 x /= 10;
72 }
73 return width;
74}
75
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -070076// Given a series of audio processing wakeup timestamps,
77// buckets the time intervals into a histogram, searches for
78// outliers, analyzes the outlier series for unexpectedly
79// small or large values and stores these as peaks, and flushes
80// the timestamp series from memory.
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070081void PerformanceAnalysis::processAndFlushTimeStampSeries() {
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -070082 // 1) analyze the series to store all outliers and their exact timestamps:
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070083 storeOutlierData(mTimeStampSeries);
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -070084
85 // 2) detect peaks in the outlier series
86 detectPeaks();
87
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070088 // 3) compute its histogram, append to mRecentHists and clear the time series
89 mRecentHists.emplace_back(static_cast<timestamp>(mTimeStampSeries[0]),
90 buildBuckets(mTimeStampSeries));
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -070091 // do not let mRecentHists exceed capacity
92 // ALOGD("mRecentHists size: %d", static_cast<int>(mRecentHists.size()));
93 if (mRecentHists.size() >= kRecentHistsCapacity) {
94 // ALOGD("popped back mRecentHists");
95 mRecentHists.pop_front();
96 }
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -070097 mTimeStampSeries.clear();
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -070098}
99
100// forces short-term histogram storage to avoid adding idle audio time interval
101// to buffer period data
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700102void PerformanceAnalysis::handleStateChange() {
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -0700103 ALOGD("handleStateChange");
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700104 processAndFlushTimeStampSeries();
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -0700105 return;
106}
107
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700108// Takes a single buffer period timestamp entry information and stores it in a
109// temporary series of timestamps. Once the series is full, the data is analyzed,
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700110// stored, and emptied.
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700111void PerformanceAnalysis::logTsEntry(int64_t ts) {
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700112 // TODO might want to filter excessively high outliers, which are usually caused
113 // by the thread being inactive.
114 // Store time series data for each reader in order to bucket it once there
115 // is enough data. Then, write to recentHists as a histogram.
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700116 mTimeStampSeries.push_back(ts);
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -0700117 // if length of the time series has reached kShortHistSize samples,
118 // analyze the data and flush the timestamp series from memory
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700119 if (mTimeStampSeries.size() >= kShortHistSize) {
120 processAndFlushTimeStampSeries();
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700121 }
122}
123
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700124// When the short-term histogram array mRecentHists has reached capacity,
125// merge histograms for data compression and store them in mLongTermHists
126// clears mRecentHists
127// TODO: have logTsEntry write directly to mLongTermHists, discard mRecentHists,
128// start a new histogram when a peak occurs
129void PerformanceAnalysis::processAndFlushRecentHists() {
130
131 // Buckets is used to aggregate short-term histograms.
132 Histogram buckets;
133 timestamp startingTs = mRecentHists[0].first;
134
135 for (const auto &shortHist: mRecentHists) {
136 // If the time between starting and ending timestamps has reached the maximum,
137 // add the current histogram (buckets) to the long-term histogram buffer,
138 // clear buckets, and start a new long-term histogram aggregation process.
139 if (deltaMs(startingTs, shortHist.first) >= kMaxHistTimespanMs) {
140 mLongTermHists.emplace_back(startingTs, std::move(buckets));
141 buckets.clear();
142 startingTs = shortHist.first;
143 // When memory is full, delete oldest histogram
144 // TODO use a circular buffer
145 if (mLongTermHists.size() >= kLongTermHistsCapacity) {
146 mLongTermHists.pop_front();
147 }
148 }
149
150 // add current histogram to buckets
151 for (const auto &countPair : shortHist.second) {
152 buckets[countPair.first] += countPair.second;
153 }
154 }
155 mRecentHists.clear();
156 // TODO: decide when/where to call writeToFile
157 // TODO: add a thread-specific extension to the file name
158 static const char* const kName = (const char *) "/data/misc/audioserver/sample_results.txt";
159 writeToFile(mOutlierData, mLongTermHists, kName, false);
160}
161
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700162// Given a series of outlier intervals (mOutlier data),
163// looks for changes in distribution (peaks), which can be either positive or negative.
164// The function sets the mean to the starting value and sigma to 0, and updates
165// them as long as no peak is detected. When a value is more than 'threshold'
166// standard deviations from the mean, a peak is detected and the mean and sigma
167// are set to the peak value and 0.
168void PerformanceAnalysis::detectPeaks() {
169 if (mOutlierData.empty()) {
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700170 return;
171 }
172
173 // compute mean of the distribution. Used to check whether a value is large
174 const double kTypicalDiff = std::accumulate(
175 mOutlierData.begin(), mOutlierData.end(), 0,
176 [](auto &a, auto &b){return a + b.first;}) / mOutlierData.size();
177 // ALOGD("typicalDiff %f", kTypicalDiff);
178
179 // iterator at the beginning of a sequence, or updated to the most recent peak
180 std::deque<std::pair<uint64_t, uint64_t>>::iterator start = mOutlierData.begin();
181 // the mean and standard deviation are updated every time a peak is detected
182 // initialize first time. The mean from the previous sequence is stored
183 // for the next sequence. Here, they are initialized for the first time.
184 if (mPeakDetectorMean < 0) {
185 mPeakDetectorMean = static_cast<double>(start->first);
186 mPeakDetectorSd = 0;
187 }
188 auto sqr = [](auto x){ return x * x; };
189 for (auto it = mOutlierData.begin(); it != mOutlierData.end(); ++it) {
190 // no surprise occurred:
191 // the new element is a small number of standard deviations from the mean
192 if ((fabs(it->first - mPeakDetectorMean) < kStddevThreshold * mPeakDetectorSd) ||
193 // or: right after peak has been detected, the delta is smaller than average
194 (mPeakDetectorSd == 0 && fabs(it->first - mPeakDetectorMean) < kTypicalDiff)) {
195 // update the mean and sd:
196 // count number of elements (distance between start interator and current)
197 const int kN = std::distance(start, it) + 1;
198 // usual formulas for mean and sd
199 mPeakDetectorMean = std::accumulate(start, it + 1, 0.0,
200 [](auto &a, auto &b){return a + b.first;}) / kN;
201 mPeakDetectorSd = sqrt(std::accumulate(start, it + 1, 0.0,
202 [=](auto &a, auto &b){ return a + sqr(b.first - mPeakDetectorMean);})) /
203 ((kN > 1)? kN - 1 : kN); // kN - 1: mean is correlated with variance
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700204 }
205 // surprising value: store peak timestamp and reset mean, sd, and start iterator
206 else {
207 mPeakTimestamps.emplace_back(it->second);
208 // TODO: remove pop_front once a circular buffer is in place
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700209 if (mPeakTimestamps.size() >= kPeakSeriesSize) {
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700210 mPeakTimestamps.pop_front();
211 }
212 mPeakDetectorMean = static_cast<double>(it->first);
213 mPeakDetectorSd = 0;
214 start = it;
215 }
216 }
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700217 return;
218}
219
220// Called by LogTsEntry. The input is a vector of timestamps.
221// Finds outliers and writes to mOutlierdata.
222// Each value in mOutlierdata consists of: <outlier timestamp, time elapsed since previous outlier>.
223// e.g. timestamps (ms) 1, 4, 5, 16, 18, 28 will produce pairs (4, 5), (13, 18).
224// This function is applied to the time series before it is converted into a histogram.
225void PerformanceAnalysis::storeOutlierData(const std::vector<int64_t> &timestamps) {
226 if (timestamps.size() < 1) {
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700227 return;
228 }
229 // first pass: need to initialize
230 if (mElapsed == 0) {
231 mPrevNs = timestamps[0];
232 }
233 for (const auto &ts: timestamps) {
234 const uint64_t diffMs = static_cast<uint64_t>(deltaMs(mPrevNs, ts));
235 if (diffMs >= static_cast<uint64_t>(kOutlierMs)) {
236 mOutlierData.emplace_back(mElapsed, static_cast<uint64_t>(mPrevNs));
237 // Remove oldest value if the vector is full
238 // TODO: remove pop_front once circular buffer is in place
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700239 // FIXME: make sure kShortHistSize is large enough that that data will never be lost
240 // before being written to file or to a FIFO
241 if (mOutlierData.size() >= kOutlierSeriesSize) {
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700242 mOutlierData.pop_front();
243 }
244 mElapsed = 0;
245 }
246 mElapsed += diffMs;
247 mPrevNs = ts;
248 }
249}
250
251
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700252// FIXME: delete this temporary test code, recycled for various new functions
253void PerformanceAnalysis::testFunction() {
254 // produces values (4: 5000000), (13: 18000000)
255 // ns timestamps of buffer periods
256 const std::vector<int64_t>kTempTestData = {1000000, 4000000, 5000000,
257 16000000, 18000000, 28000000};
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700258 PerformanceAnalysis::storeOutlierData(kTempTestData);
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700259 for (const auto &outlier: mOutlierData) {
260 ALOGE("PerformanceAnalysis test %lld: %lld",
261 static_cast<long long>(outlier.first), static_cast<long long>(outlier.second));
262 }
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700263 detectPeaks();
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700264}
265
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700266// 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 -0700267// TODO consider changing all ints to uint32_t or uint64_t
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700268// TODO: move this to ReportPerformance, probably make it a friend function of PerformanceAnalysis
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700269void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
270 if (mRecentHists.size() < 1) {
271 ALOGD("reportPerformance: mRecentHists is empty");
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700272 return;
273 }
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700274 ALOGD("reportPerformance: hists size %d", static_cast<int>(mRecentHists.size()));
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700275 // TODO: more elaborate data analysis
276 std::map<int, int> buckets;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700277 for (const auto &shortHist: mRecentHists) {
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700278 for (const auto &countPair : shortHist.second) {
279 buckets[countPair.first] += countPair.second;
280 }
281 }
282
283 // underscores and spaces length corresponds to maximum width of histogram
284 static const int kLen = 40;
285 std::string underscores(kLen, '_');
286 std::string spaces(kLen, ' ');
287
288 auto it = buckets.begin();
289 int maxDelta = it->first;
290 int maxCount = it->second;
291 // Compute maximum values
292 while (++it != buckets.end()) {
293 if (it->first > maxDelta) {
294 maxDelta = it->first;
295 }
296 if (it->second > maxCount) {
297 maxCount = it->second;
298 }
299 }
300 int height = log2(maxCount) + 1; // maxCount > 0, safe to call log2
301 const int leftPadding = widthOf(1 << height);
302 const int colWidth = std::max(std::max(widthOf(maxDelta) + 1, 3), leftPadding + 2);
303 int scalingFactor = 1;
304 // scale data if it exceeds maximum height
305 if (height > maxHeight) {
306 scalingFactor = (height + maxHeight) / maxHeight;
307 height /= scalingFactor;
308 }
309 body->appendFormat("\n%*s", leftPadding + 11, "Occurrences");
310 // write histogram label line with bucket values
311 body->appendFormat("\n%s", " ");
312 body->appendFormat("%*s", leftPadding, " ");
313 for (auto const &x : buckets) {
314 body->appendFormat("%*d", colWidth, x.second);
315 }
316 // write histogram ascii art
317 body->appendFormat("\n%s", " ");
318 for (int row = height * scalingFactor; row >= 0; row -= scalingFactor) {
319 const int value = 1 << row;
320 body->appendFormat("%.*s", leftPadding, spaces.c_str());
321 for (auto const &x : buckets) {
322 body->appendFormat("%.*s%s", colWidth - 1, 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) * colWidth, 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 body->appendFormat("%*d", colWidth, x.first);
336 }
337 body->appendFormat("%.*s%s", colWidth, spaces.c_str(), "ms\n");
338
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700339 // Now report glitches
340 body->appendFormat("\ntime elapsed between glitches and glitch timestamps\n");
341 for (const auto &outlier: mOutlierData) {
342 body->appendFormat("%lld: %lld\n", static_cast<long long>(outlier.first),
343 static_cast<long long>(outlier.second));
344 }
345
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700346}
347
348
349// Produces a log warning if the timing of recent buffer periods caused a glitch
350// Computes sum of running window of three buffer periods
351// Checks whether the buffer periods leave enough CPU time for the next one
352// e.g. if a buffer period is expected to be 4 ms and a buffer requires 3 ms of CPU time,
353// here are some glitch cases:
354// 4 + 4 + 6 ; 5 + 4 + 5; 2 + 2 + 10
355// TODO: develop this code to track changes in histogram distribution in addition
356// to / instead of glitches.
357void PerformanceAnalysis::alertIfGlitch(const std::vector<int64_t> &samples) {
358 std::deque<int> periods(kNumBuff, kPeriodMs);
359 for (size_t i = 2; i < samples.size(); ++i) { // skip first time entry
360 periods.push_front(deltaMs(samples[i - 1], samples[i]));
361 periods.pop_back();
362 // TODO: check that all glitch cases are covered
363 if (std::accumulate(periods.begin(), periods.end(), 0) > kNumBuff * kPeriodMs +
364 kPeriodMs - kPeriodMsCPU) {
365 ALOGW("A glitch occurred");
366 periods.assign(kNumBuff, kPeriodMs);
367 }
368 }
369 return;
370}
371
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700372} // namespace ReportPerformance
373
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700374} // namespace android