blob: 37d6d9f176344054e1a24fcbd9ee42ca6332fd37 [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()) {
72 ALOGD("Timestamp series is empty");
73 return;
74 }
75
76 // mHists is empty if program just started
77 if (mHists.empty()) {
78 mHists.emplace_front(static_cast<uint64_t>(mTimeStampSeries[0]),
79 std::map<int, int>());
80 }
81
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 Wagera80649a2017-07-21 16:16:38 -070088 // if the current histogram has spanned its maximum time interval,
89 // insert a new empty histogram to the front of mHists
90 if (deltaMs(mHists[0].first, mTimeStampSeries[0]) >= kMaxHistTimespanMs) {
91 mHists.emplace_front(static_cast<uint64_t>(mTimeStampSeries[0]),
92 std::map<int, int>());
93 // When memory is full, delete oldest histogram
94 if (mHists.size() >= kHistsCapacity) {
95 mHists.resize(kHistsCapacity);
96 }
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -070097 }
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -070098
99 // 3) add current time intervals to histogram
100 for (size_t i = 1; i < mTimeStampSeries.size(); ++i) {
101 ++mHists[0].second[deltaMs(
102 mTimeStampSeries[i - 1], mTimeStampSeries[i])];
103 }
104
105 // clear the timestamps
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700106 mTimeStampSeries.clear();
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 Wagera80649a2017-07-21 16:16:38 -0700128 if (mTimeStampSeries.size() >= kHistSize) {
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 Wagera80649a2017-07-21 16:16:38 -0700133// TODO: move this someplace
134// static const char* const kName = (const char *) "/data/misc/audioserver/sample_results.txt";
135// writeToFile(mOutlierData, mLongTermHists, kName, false);
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700136
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700137// Given a series of outlier intervals (mOutlier data),
138// looks for changes in distribution (peaks), which can be either positive or negative.
139// The function sets the mean to the starting value and sigma to 0, and updates
140// them as long as no peak is detected. When a value is more than 'threshold'
141// standard deviations from the mean, a peak is detected and the mean and sigma
142// are set to the peak value and 0.
143void PerformanceAnalysis::detectPeaks() {
144 if (mOutlierData.empty()) {
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700145 return;
146 }
147
148 // compute mean of the distribution. Used to check whether a value is large
149 const double kTypicalDiff = std::accumulate(
150 mOutlierData.begin(), mOutlierData.end(), 0,
151 [](auto &a, auto &b){return a + b.first;}) / mOutlierData.size();
152 // ALOGD("typicalDiff %f", kTypicalDiff);
153
154 // iterator at the beginning of a sequence, or updated to the most recent peak
155 std::deque<std::pair<uint64_t, uint64_t>>::iterator start = mOutlierData.begin();
156 // the mean and standard deviation are updated every time a peak is detected
157 // initialize first time. The mean from the previous sequence is stored
158 // for the next sequence. Here, they are initialized for the first time.
159 if (mPeakDetectorMean < 0) {
160 mPeakDetectorMean = static_cast<double>(start->first);
161 mPeakDetectorSd = 0;
162 }
163 auto sqr = [](auto x){ return x * x; };
164 for (auto it = mOutlierData.begin(); it != mOutlierData.end(); ++it) {
165 // no surprise occurred:
166 // the new element is a small number of standard deviations from the mean
167 if ((fabs(it->first - mPeakDetectorMean) < kStddevThreshold * mPeakDetectorSd) ||
168 // or: right after peak has been detected, the delta is smaller than average
169 (mPeakDetectorSd == 0 && fabs(it->first - mPeakDetectorMean) < kTypicalDiff)) {
170 // update the mean and sd:
171 // count number of elements (distance between start interator and current)
172 const int kN = std::distance(start, it) + 1;
173 // usual formulas for mean and sd
174 mPeakDetectorMean = std::accumulate(start, it + 1, 0.0,
175 [](auto &a, auto &b){return a + b.first;}) / kN;
176 mPeakDetectorSd = sqrt(std::accumulate(start, it + 1, 0.0,
177 [=](auto &a, auto &b){ return a + sqr(b.first - mPeakDetectorMean);})) /
178 ((kN > 1)? kN - 1 : kN); // kN - 1: mean is correlated with variance
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700179 }
180 // surprising value: store peak timestamp and reset mean, sd, and start iterator
181 else {
182 mPeakTimestamps.emplace_back(it->second);
183 // TODO: remove pop_front once a circular buffer is in place
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700184 if (mPeakTimestamps.size() >= kPeakSeriesSize) {
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700185 mPeakTimestamps.pop_front();
186 }
187 mPeakDetectorMean = static_cast<double>(it->first);
188 mPeakDetectorSd = 0;
189 start = it;
190 }
191 }
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700192 return;
193}
194
195// Called by LogTsEntry. The input is a vector of timestamps.
196// Finds outliers and writes to mOutlierdata.
197// Each value in mOutlierdata consists of: <outlier timestamp, time elapsed since previous outlier>.
198// e.g. timestamps (ms) 1, 4, 5, 16, 18, 28 will produce pairs (4, 5), (13, 18).
199// This function is applied to the time series before it is converted into a histogram.
200void PerformanceAnalysis::storeOutlierData(const std::vector<int64_t> &timestamps) {
201 if (timestamps.size() < 1) {
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700202 return;
203 }
204 // first pass: need to initialize
205 if (mElapsed == 0) {
206 mPrevNs = timestamps[0];
207 }
208 for (const auto &ts: timestamps) {
209 const uint64_t diffMs = static_cast<uint64_t>(deltaMs(mPrevNs, ts));
210 if (diffMs >= static_cast<uint64_t>(kOutlierMs)) {
211 mOutlierData.emplace_back(mElapsed, static_cast<uint64_t>(mPrevNs));
212 // Remove oldest value if the vector is full
213 // TODO: remove pop_front once circular buffer is in place
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700214 // FIXME: make sure kShortHistSize is large enough that that data will never be lost
215 // before being written to file or to a FIFO
216 if (mOutlierData.size() >= kOutlierSeriesSize) {
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700217 mOutlierData.pop_front();
218 }
219 mElapsed = 0;
220 }
221 mElapsed += diffMs;
222 mPrevNs = ts;
223 }
224}
225
226
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700227// FIXME: delete this temporary test code, recycled for various new functions
228void PerformanceAnalysis::testFunction() {
229 // produces values (4: 5000000), (13: 18000000)
230 // ns timestamps of buffer periods
231 const std::vector<int64_t>kTempTestData = {1000000, 4000000, 5000000,
232 16000000, 18000000, 28000000};
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700233 PerformanceAnalysis::storeOutlierData(kTempTestData);
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700234 for (const auto &outlier: mOutlierData) {
235 ALOGE("PerformanceAnalysis test %lld: %lld",
236 static_cast<long long>(outlier.first), static_cast<long long>(outlier.second));
237 }
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700238 detectPeaks();
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700239}
240
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700241// 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 -0700242// TODO consider changing all ints to uint32_t or uint64_t
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700243// TODO: move this to ReportPerformance, probably make it a friend function of PerformanceAnalysis
Sanna Catherine de Treville Wagercf6c75a2017-07-21 17:05:25 -0700244void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) const {
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -0700245 if (mHists.empty()) {
246 ALOGD("reportPerformance: mHists is empty");
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700247 return;
248 }
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -0700249 ALOGD("reportPerformance: hists size %d", static_cast<int>(mHists.size()));
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700250 // TODO: more elaborate data analysis
251 std::map<int, int> buckets;
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -0700252 for (const auto &shortHist: mHists) {
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700253 for (const auto &countPair : shortHist.second) {
254 buckets[countPair.first] += countPair.second;
255 }
256 }
257
258 // underscores and spaces length corresponds to maximum width of histogram
259 static const int kLen = 40;
260 std::string underscores(kLen, '_');
261 std::string spaces(kLen, ' ');
262
263 auto it = buckets.begin();
264 int maxDelta = it->first;
265 int maxCount = it->second;
266 // Compute maximum values
267 while (++it != buckets.end()) {
268 if (it->first > maxDelta) {
269 maxDelta = it->first;
270 }
271 if (it->second > maxCount) {
272 maxCount = it->second;
273 }
274 }
275 int height = log2(maxCount) + 1; // maxCount > 0, safe to call log2
276 const int leftPadding = widthOf(1 << height);
277 const int colWidth = std::max(std::max(widthOf(maxDelta) + 1, 3), leftPadding + 2);
278 int scalingFactor = 1;
279 // scale data if it exceeds maximum height
280 if (height > maxHeight) {
281 scalingFactor = (height + maxHeight) / maxHeight;
282 height /= scalingFactor;
283 }
Sanna Catherine de Treville Wagere4865262017-07-14 16:24:15 -0700284 // TODO: print reader (author) ID
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700285 body->appendFormat("\n%*s", leftPadding + 11, "Occurrences");
286 // write histogram label line with bucket values
287 body->appendFormat("\n%s", " ");
288 body->appendFormat("%*s", leftPadding, " ");
289 for (auto const &x : buckets) {
290 body->appendFormat("%*d", colWidth, x.second);
291 }
292 // write histogram ascii art
293 body->appendFormat("\n%s", " ");
294 for (int row = height * scalingFactor; row >= 0; row -= scalingFactor) {
295 const int value = 1 << row;
296 body->appendFormat("%.*s", leftPadding, spaces.c_str());
297 for (auto const &x : buckets) {
298 body->appendFormat("%.*s%s", colWidth - 1, spaces.c_str(), x.second < value ? " " : "|");
299 }
300 body->appendFormat("\n%s", " ");
301 }
302 // print x-axis
303 const int columns = static_cast<int>(buckets.size());
304 body->appendFormat("%*c", leftPadding, ' ');
305 body->appendFormat("%.*s", (columns + 1) * colWidth, underscores.c_str());
306 body->appendFormat("\n%s", " ");
307
308 // write footer with bucket labels
309 body->appendFormat("%*s", leftPadding, " ");
310 for (auto const &x : buckets) {
311 body->appendFormat("%*d", colWidth, x.first);
312 }
313 body->appendFormat("%.*s%s", colWidth, spaces.c_str(), "ms\n");
314
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700315 // Now report glitches
316 body->appendFormat("\ntime elapsed between glitches and glitch timestamps\n");
317 for (const auto &outlier: mOutlierData) {
318 body->appendFormat("%lld: %lld\n", static_cast<long long>(outlier.first),
319 static_cast<long long>(outlier.second));
320 }
321
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700322}
323
324
325// Produces a log warning if the timing of recent buffer periods caused a glitch
326// Computes sum of running window of three buffer periods
327// Checks whether the buffer periods leave enough CPU time for the next one
328// e.g. if a buffer period is expected to be 4 ms and a buffer requires 3 ms of CPU time,
329// here are some glitch cases:
330// 4 + 4 + 6 ; 5 + 4 + 5; 2 + 2 + 10
331// TODO: develop this code to track changes in histogram distribution in addition
332// to / instead of glitches.
333void PerformanceAnalysis::alertIfGlitch(const std::vector<int64_t> &samples) {
334 std::deque<int> periods(kNumBuff, kPeriodMs);
335 for (size_t i = 2; i < samples.size(); ++i) { // skip first time entry
336 periods.push_front(deltaMs(samples[i - 1], samples[i]));
337 periods.pop_back();
338 // TODO: check that all glitch cases are covered
339 if (std::accumulate(periods.begin(), periods.end(), 0) > kNumBuff * kPeriodMs +
340 kPeriodMs - kPeriodMsCPU) {
341 ALOGW("A glitch occurred");
342 periods.assign(kNumBuff, kPeriodMs);
343 }
344 }
345 return;
346}
347
Sanna Catherine de Treville Wagercf6c75a2017-07-21 17:05:25 -0700348//------------------------------------------------------------------------------
349
350// writes summary of performance into specified file descriptor
351void dump(int fd, int indent, const std::map<int, PerformanceAnalysis>
352 &threadPerformanceAnalysis) {
353 String8 body;
354 for (auto & thread : threadPerformanceAnalysis) {
355 thread.second.reportPerformance(&body);
356 }
357 if (!body.isEmpty()) {
358 dumpLine(fd, indent, body);
359 body.clear();
360 }
361}
362
363// Writes a string into specified file descriptor
364void dumpLine(int fd, int indent, const String8 &body) {
365 dprintf(fd, "%.*s%s \n", indent, "", body.string());
366}
367
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700368} // namespace ReportPerformance
369
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700370} // namespace android