blob: fa8f47ef8265df08c675c3784d60ca27fe8f3ed5 [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>
24#include <fstream>
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -070025#include <iostream>
26#include <math.h>
27#include <numeric>
28#include <vector>
29#include <stdarg.h>
30#include <stdint.h>
31#include <stdio.h>
32#include <string.h>
33#include <sys/prctl.h>
34#include <time.h>
35#include <new>
36#include <audio_utils/roundup.h>
37#include <media/nbaio/NBLog.h>
38#include <media/nbaio/PerformanceAnalysis.h>
39// #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 Wager41cad592017-06-29 14:57:59 -070048PerformanceAnalysis::PerformanceAnalysis() {
49 // These variables will be (FIXME) learned from the data
50 kPeriodMs = 4; // typical buffer period (mode)
51 // average number of Ms spent processing buffer
52 kPeriodMsCPU = static_cast<int>(kPeriodMs * kRatio);
53}
54
55// converts a time series into a map. key: buffer period length. value: count
56static std::map<int, int> buildBuckets(const std::vector<int64_t> &samples) {
57 // TODO allow buckets of variable resolution
58 std::map<int, int> buckets;
59 for (size_t i = 1; i < samples.size(); ++i) {
60 ++buckets[deltaMs(samples[i - 1], samples[i])];
61 }
62 return buckets;
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -070063}
64
65static int widthOf(int x) {
66 int width = 0;
67 while (x > 0) {
68 ++width;
69 x /= 10;
70 }
71 return width;
72}
73
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -070074// Takes a single buffer period timestamp entry with author information and stores it
75// in a temporary series of timestamps. Once the series is full, the data is analyzed,
76// stored, and emptied.
77// TODO: decide whether author or file location information is more important to store
78// for now, only stores author (thread)
79void PerformanceAnalysis::logTsEntry(int author, int64_t ts) {
80 // TODO might want to filter excessively high outliers, which are usually caused
81 // by the thread being inactive.
82 // Store time series data for each reader in order to bucket it once there
83 // is enough data. Then, write to recentHists as a histogram.
84 mTimeStampSeries[author].push_back(ts);
85 // if length of the time series has reached kShortHistSize samples, do 1) and 2):
86 if (mTimeStampSeries[author].size() >= kShortHistSize) {
87 // 1) analyze the series to store all outliers and their exact timestamps:
88 storeOutlierData(mTimeStampSeries[author]);
89 // 2) detect peaks in the outlier series
90 detectPeaks();
91 // 3) compute its histogram, append this to mRecentHists and erase the time series
92 // FIXME: need to store the timestamp of the beginning of each histogram
93 // FIXME: Restore LOG_HIST_FLUSH to separate histograms at every end-of-stream event
94 // A histogram should not span data between audio off/on timespans
95 mRecentHists.emplace_back(author,
96 buildBuckets(mTimeStampSeries[author]));
97 // do not let mRecentHists exceed capacity
98 // ALOGD("mRecentHists size: %d", static_cast<int>(mRecentHists.size()));
99 if (mRecentHists.size() >= kRecentHistsCapacity) {
100 // ALOGD("popped back mRecentHists");
101 mRecentHists.pop_front();
102 }
103 mTimeStampSeries[author].clear();
104 }
105}
106
107// Given a series of outlier intervals (mOutlier data),
108// looks for changes in distribution (peaks), which can be either positive or negative.
109// The function sets the mean to the starting value and sigma to 0, and updates
110// them as long as no peak is detected. When a value is more than 'threshold'
111// standard deviations from the mean, a peak is detected and the mean and sigma
112// are set to the peak value and 0.
113void PerformanceAnalysis::detectPeaks() {
114 if (mOutlierData.empty()) {
115 ALOGD("peak detector called on empty array");
116 return;
117 }
118
119 // compute mean of the distribution. Used to check whether a value is large
120 const double kTypicalDiff = std::accumulate(
121 mOutlierData.begin(), mOutlierData.end(), 0,
122 [](auto &a, auto &b){return a + b.first;}) / mOutlierData.size();
123 // ALOGD("typicalDiff %f", kTypicalDiff);
124
125 // iterator at the beginning of a sequence, or updated to the most recent peak
126 std::deque<std::pair<uint64_t, uint64_t>>::iterator start = mOutlierData.begin();
127 // the mean and standard deviation are updated every time a peak is detected
128 // initialize first time. The mean from the previous sequence is stored
129 // for the next sequence. Here, they are initialized for the first time.
130 if (mPeakDetectorMean < 0) {
131 mPeakDetectorMean = static_cast<double>(start->first);
132 mPeakDetectorSd = 0;
133 }
134 auto sqr = [](auto x){ return x * x; };
135 for (auto it = mOutlierData.begin(); it != mOutlierData.end(); ++it) {
136 // no surprise occurred:
137 // the new element is a small number of standard deviations from the mean
138 if ((fabs(it->first - mPeakDetectorMean) < kStddevThreshold * mPeakDetectorSd) ||
139 // or: right after peak has been detected, the delta is smaller than average
140 (mPeakDetectorSd == 0 && fabs(it->first - mPeakDetectorMean) < kTypicalDiff)) {
141 // update the mean and sd:
142 // count number of elements (distance between start interator and current)
143 const int kN = std::distance(start, it) + 1;
144 // usual formulas for mean and sd
145 mPeakDetectorMean = std::accumulate(start, it + 1, 0.0,
146 [](auto &a, auto &b){return a + b.first;}) / kN;
147 mPeakDetectorSd = sqrt(std::accumulate(start, it + 1, 0.0,
148 [=](auto &a, auto &b){ return a + sqr(b.first - mPeakDetectorMean);})) /
149 ((kN > 1)? kN - 1 : kN); // kN - 1: mean is correlated with variance
150 // ALOGD("value, mean, sd: %f, %f, %f", static_cast<double>(it->first), mean, sd);
151 }
152 // surprising value: store peak timestamp and reset mean, sd, and start iterator
153 else {
154 mPeakTimestamps.emplace_back(it->second);
155 // TODO: remove pop_front once a circular buffer is in place
156 if (mPeakTimestamps.size() >= kShortHistSize) {
157 ALOGD("popped back mPeakTimestamps");
158 mPeakTimestamps.pop_front();
159 }
160 mPeakDetectorMean = static_cast<double>(it->first);
161 mPeakDetectorSd = 0;
162 start = it;
163 }
164 }
165 //for (const auto &it : mPeakTimestamps) {
166 // ALOGE("mPeakTimestamps %f", static_cast<double>(it));
167 //}
168 return;
169}
170
171// Called by LogTsEntry. The input is a vector of timestamps.
172// Finds outliers and writes to mOutlierdata.
173// Each value in mOutlierdata consists of: <outlier timestamp, time elapsed since previous outlier>.
174// e.g. timestamps (ms) 1, 4, 5, 16, 18, 28 will produce pairs (4, 5), (13, 18).
175// This function is applied to the time series before it is converted into a histogram.
176void PerformanceAnalysis::storeOutlierData(const std::vector<int64_t> &timestamps) {
177 if (timestamps.size() < 1) {
178 ALOGE("storeOutlierData called on empty vector");
179 return;
180 }
181 // first pass: need to initialize
182 if (mElapsed == 0) {
183 mPrevNs = timestamps[0];
184 }
185 for (const auto &ts: timestamps) {
186 const uint64_t diffMs = static_cast<uint64_t>(deltaMs(mPrevNs, ts));
187 if (diffMs >= static_cast<uint64_t>(kOutlierMs)) {
188 mOutlierData.emplace_back(mElapsed, static_cast<uint64_t>(mPrevNs));
189 // Remove oldest value if the vector is full
190 // TODO: remove pop_front once circular buffer is in place
191 // FIXME: change kShortHistSize to some other constant. Make sure it is large
192 // enough that data will never be lost before being written to a long-term FIFO
193 if (mOutlierData.size() >= kShortHistSize) {
194 ALOGD("popped back mOutlierData");
195 mOutlierData.pop_front();
196 }
197 mElapsed = 0;
198 }
199 mElapsed += diffMs;
200 mPrevNs = ts;
201 }
202}
203
204
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700205// FIXME: delete this temporary test code, recycled for various new functions
206void PerformanceAnalysis::testFunction() {
207 // produces values (4: 5000000), (13: 18000000)
208 // ns timestamps of buffer periods
209 const std::vector<int64_t>kTempTestData = {1000000, 4000000, 5000000,
210 16000000, 18000000, 28000000};
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700211 PerformanceAnalysis::storeOutlierData(kTempTestData);
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700212 for (const auto &outlier: mOutlierData) {
213 ALOGE("PerformanceAnalysis test %lld: %lld",
214 static_cast<long long>(outlier.first), static_cast<long long>(outlier.second));
215 }
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700216 detectPeaks();
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700217}
218
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700219// 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 -0700220// FIXME: as can be seen when printing the values, the outlier timestamps typically occur
221// in the first histogram 35 to 38 indices from the end (most often 35).
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700222// TODO consider changing all ints to uint32_t or uint64_t
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700223void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
224 if (mRecentHists.size() < 1) {
225 ALOGD("reportPerformance: mRecentHists is empty");
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700226 return;
227 }
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700228 ALOGD("reportPerformance: hists size %d", static_cast<int>(mRecentHists.size()));
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700229 // TODO: more elaborate data analysis
230 std::map<int, int> buckets;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700231 for (const auto &shortHist: mRecentHists) {
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700232 for (const auto &countPair : shortHist.second) {
233 buckets[countPair.first] += countPair.second;
234 }
235 }
236
237 // underscores and spaces length corresponds to maximum width of histogram
238 static const int kLen = 40;
239 std::string underscores(kLen, '_');
240 std::string spaces(kLen, ' ');
241
242 auto it = buckets.begin();
243 int maxDelta = it->first;
244 int maxCount = it->second;
245 // Compute maximum values
246 while (++it != buckets.end()) {
247 if (it->first > maxDelta) {
248 maxDelta = it->first;
249 }
250 if (it->second > maxCount) {
251 maxCount = it->second;
252 }
253 }
254 int height = log2(maxCount) + 1; // maxCount > 0, safe to call log2
255 const int leftPadding = widthOf(1 << height);
256 const int colWidth = std::max(std::max(widthOf(maxDelta) + 1, 3), leftPadding + 2);
257 int scalingFactor = 1;
258 // scale data if it exceeds maximum height
259 if (height > maxHeight) {
260 scalingFactor = (height + maxHeight) / maxHeight;
261 height /= scalingFactor;
262 }
263 body->appendFormat("\n%*s", leftPadding + 11, "Occurrences");
264 // write histogram label line with bucket values
265 body->appendFormat("\n%s", " ");
266 body->appendFormat("%*s", leftPadding, " ");
267 for (auto const &x : buckets) {
268 body->appendFormat("%*d", colWidth, x.second);
269 }
270 // write histogram ascii art
271 body->appendFormat("\n%s", " ");
272 for (int row = height * scalingFactor; row >= 0; row -= scalingFactor) {
273 const int value = 1 << row;
274 body->appendFormat("%.*s", leftPadding, spaces.c_str());
275 for (auto const &x : buckets) {
276 body->appendFormat("%.*s%s", colWidth - 1, spaces.c_str(), x.second < value ? " " : "|");
277 }
278 body->appendFormat("\n%s", " ");
279 }
280 // print x-axis
281 const int columns = static_cast<int>(buckets.size());
282 body->appendFormat("%*c", leftPadding, ' ');
283 body->appendFormat("%.*s", (columns + 1) * colWidth, underscores.c_str());
284 body->appendFormat("\n%s", " ");
285
286 // write footer with bucket labels
287 body->appendFormat("%*s", leftPadding, " ");
288 for (auto const &x : buckets) {
289 body->appendFormat("%*d", colWidth, x.first);
290 }
291 body->appendFormat("%.*s%s", colWidth, spaces.c_str(), "ms\n");
292
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700293 // Now report glitches
294 body->appendFormat("\ntime elapsed between glitches and glitch timestamps\n");
295 for (const auto &outlier: mOutlierData) {
296 body->appendFormat("%lld: %lld\n", static_cast<long long>(outlier.first),
297 static_cast<long long>(outlier.second));
298 }
299
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700300}
301
302
303// Produces a log warning if the timing of recent buffer periods caused a glitch
304// Computes sum of running window of three buffer periods
305// Checks whether the buffer periods leave enough CPU time for the next one
306// e.g. if a buffer period is expected to be 4 ms and a buffer requires 3 ms of CPU time,
307// here are some glitch cases:
308// 4 + 4 + 6 ; 5 + 4 + 5; 2 + 2 + 10
309// TODO: develop this code to track changes in histogram distribution in addition
310// to / instead of glitches.
311void PerformanceAnalysis::alertIfGlitch(const std::vector<int64_t> &samples) {
312 std::deque<int> periods(kNumBuff, kPeriodMs);
313 for (size_t i = 2; i < samples.size(); ++i) { // skip first time entry
314 periods.push_front(deltaMs(samples[i - 1], samples[i]));
315 periods.pop_back();
316 // TODO: check that all glitch cases are covered
317 if (std::accumulate(periods.begin(), periods.end(), 0) > kNumBuff * kPeriodMs +
318 kPeriodMs - kPeriodMsCPU) {
319 ALOGW("A glitch occurred");
320 periods.assign(kNumBuff, kPeriodMs);
321 }
322 }
323 return;
324}
325
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700326} // namespace android