blob: 8c2104cd1236cf26d61447f656014cbfa4229d59 [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 Wagerd0dfe432017-06-22 15:09:38 -070049static int widthOf(int x) {
50 int width = 0;
51 while (x > 0) {
52 ++width;
53 x /= 10;
54 }
55 return width;
56}
57
Sanna Catherine de Treville Wager85768942017-07-26 20:17:30 -070058
59// Given a the most recent timestamp of a series of audio processing
60// wakeup timestamps,
61// buckets the time interval into a histogram, searches for
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -070062// outliers, analyzes the outlier series for unexpectedly
Sanna Catherine de Treville Wager85768942017-07-26 20:17:30 -070063// small or large values and stores these as peaks
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -070064void PerformanceAnalysis::logTsEntry(timestamp ts) {
Sanna Catherine de Treville Wager85768942017-07-26 20:17:30 -070065 // after a state change, start a new series and do not
66 // record time intervals in-between
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -070067 if (mBufferPeriod.mPrevTs == 0) {
68 mBufferPeriod.mPrevTs = ts;
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -070069 return;
70 }
71
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -070072 // calculate time interval between current and previous timestamp
73 const msInterval diffMs = static_cast<msInterval>(
74 deltaMs(mBufferPeriod.mPrevTs, ts));
75
76 const int diffJiffy = deltaJiffy(mBufferPeriod.mPrevTs, ts);
77
78
79 // update buffer period distribution
80 // old versus new weight ratio when updating the buffer period mean
81 static constexpr double exponentialWeight = 0.999;
82 // update buffer period mean with exponential weighting
83 mBufferPeriod.mMean = (mBufferPeriod.mMean < 0) ? diffMs :
84 exponentialWeight * mBufferPeriod.mMean + (1.0 - exponentialWeight) * diffMs;
85 // set mOutlierFactor to a smaller value for the fastmixer thread
86 const int kFastMixerMax = 10;
87 // NormalMixer times vary much more than FastMixer times.
88 // TODO: mOutlierFactor values are set empirically based on what appears to be
89 // an outlier. Learn these values from the data.
90 mBufferPeriod.mOutlierFactor = mBufferPeriod.mMean < kFastMixerMax ? 1.8 : 2.5;
91 // set outlier threshold
92 mBufferPeriod.mOutlier = mBufferPeriod.mMean * mBufferPeriod.mOutlierFactor;
93
Sanna Catherine de Treville Wager85768942017-07-26 20:17:30 -070094 // Check whether the time interval between the current timestamp
95 // and the previous one is long enough to count as an outlier
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -070096 const bool isOutlier = detectAndStoreOutlier(diffMs);
Sanna Catherine de Treville Wager85768942017-07-26 20:17:30 -070097 // If an outlier was found, check whether it was a peak
98 if (isOutlier) {
99 /*bool isPeak =*/ detectAndStorePeak(
100 mOutlierData[0].first, mOutlierData[0].second);
101 // TODO: decide whether to insert a new empty histogram if a peak
102 // TODO: remove isPeak if unused to avoid "unused variable" error
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700103 // occurred at the current timestamp
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -0700104 }
105
Sanna Catherine de Treville Wager85768942017-07-26 20:17:30 -0700106 // Insert a histogram to mHists if it is empty, or
107 // close the current histogram and insert a new empty one if
108 // if the current histogram has spanned its maximum time interval.
109 if (mHists.empty() ||
110 deltaMs(mHists[0].first, ts) >= kMaxLength.HistTimespanMs) {
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700111 mHists.emplace_front(ts, std::map<int, int>());
Sanna Catherine de Treville Wager85768942017-07-26 20:17:30 -0700112 // When memory is full, delete oldest histogram
113 // TODO: use a circular buffer
114 if (mHists.size() >= kMaxLength.Hists) {
115 mHists.resize(kMaxLength.Hists);
116 }
117 }
118 // add current time intervals to histogram
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700119 ++mHists[0].second[diffJiffy];
Sanna Catherine de Treville Wager85768942017-07-26 20:17:30 -0700120 // update previous timestamp
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700121 mBufferPeriod.mPrevTs = ts;
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -0700122}
123
Sanna Catherine de Treville Wager85768942017-07-26 20:17:30 -0700124
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -0700125// forces short-term histogram storage to avoid adding idle audio time interval
126// to buffer period data
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700127void PerformanceAnalysis::handleStateChange() {
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700128 mBufferPeriod.mPrevTs = 0;
Sanna Catherine de Treville Wagera8a8a472017-07-11 09:41:25 -0700129 return;
130}
131
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700132
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 Wager6ad40ee2017-07-28 10:10:55 -0700140bool PerformanceAnalysis::detectAndStorePeak(msInterval diff, timestamp ts) {
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700141 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 return false;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700144 }
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700145 // Update mean of the distribution
146 // TypicalDiff is used to check whether a value is unusually large
147 // when we cannot use standard deviations from the mean because the sd is set to 0.
148 mOutlierDistribution.mTypicalDiff = (mOutlierDistribution.mTypicalDiff *
149 (mOutlierData.size() - 1) + diff) / mOutlierData.size();
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700150
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700151 // Initialize short-term mean at start of program
152 if (mOutlierDistribution.mMean == 0) {
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700153 mOutlierDistribution.mMean = diff;
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700154 }
155 // Update length of current sequence of outliers
156 mOutlierDistribution.mN++;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700157
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700158 // Check whether a large deviation from the mean occurred.
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700159 // If the standard deviation has been reset to zero, the comparison is
160 // instead to the mean of the full mOutlierInterval sequence.
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700161 if ((fabs(diff - mOutlierDistribution.mMean) <
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700162 mOutlierDistribution.kMaxDeviation * mOutlierDistribution.mSd) ||
163 (mOutlierDistribution.mSd == 0 &&
164 fabs(diff - mOutlierDistribution.mMean) <
165 mOutlierDistribution.mTypicalDiff)) {
166 // update the mean and sd using online algorithm
167 // https://en.wikipedia.org/wiki/
168 // Algorithms_for_calculating_variance#Online_algorithm
169 mOutlierDistribution.mN++;
170 const double kDelta = diff - mOutlierDistribution.mMean;
171 mOutlierDistribution.mMean += kDelta / mOutlierDistribution.mN;
172 const double kDelta2 = diff - mOutlierDistribution.mMean;
173 mOutlierDistribution.mM2 += kDelta * kDelta2;
174 mOutlierDistribution.mSd = (mOutlierDistribution.mN < 2) ? 0 :
Sanna Catherine de Treville Wager85768942017-07-26 20:17:30 -0700175 sqrt(mOutlierDistribution.mM2 / (mOutlierDistribution.mN - 1));
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700176 } else {
177 // new value is far from the mean:
178 // store peak timestamp and reset mean, sd, and short-term sequence
179 isPeak = true;
180 mPeakTimestamps.emplace_front(ts);
181 // if mPeaks has reached capacity, delete oldest data
182 // Note: this means that mOutlierDistribution values do not exactly
183 // match the data we have in mPeakTimestamps, but this is not an issue
184 // in practice for estimating future peaks.
185 // TODO: turn this into a circular buffer
186 if (mPeakTimestamps.size() >= kMaxLength.Peaks) {
187 mPeakTimestamps.resize(kMaxLength.Peaks);
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700188 }
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700189 mOutlierDistribution.mMean = 0;
190 mOutlierDistribution.mSd = 0;
191 mOutlierDistribution.mN = 0;
192 mOutlierDistribution.mM2 = 0;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700193 }
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700194 return isPeak;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700195}
196
Sanna Catherine de Treville Wager85768942017-07-26 20:17:30 -0700197
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700198// Determines whether the difference between a timestamp and the previous
199// one is beyond a threshold. If yes, stores the timestamp as an outlier
200// and writes to mOutlierdata in the following format:
201// Time elapsed since previous outlier: Timestamp of start of outlier
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700202// e.g. timestamps (ms) 1, 4, 5, 16, 18, 28 will produce pairs (4, 5), (13, 18).
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700203bool PerformanceAnalysis::detectAndStoreOutlier(const msInterval diffMs) {
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700204 bool isOutlier = false;
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700205 if (diffMs >= mBufferPeriod.mOutlier) {
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700206 isOutlier = true;
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700207 mOutlierData.emplace_front(
208 mOutlierDistribution.mElapsed, mBufferPeriod.mPrevTs);
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700209 // Remove oldest value if the vector is full
210 // TODO: turn this into a circular buffer
211 // TODO: make sure kShortHistSize is large enough that that data will never be lost
212 // before being written to file or to a FIFO
213 if (mOutlierData.size() >= kMaxLength.Outliers) {
214 mOutlierData.resize(kMaxLength.Outliers);
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700215 }
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700216 mOutlierDistribution.mElapsed = 0;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700217 }
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700218 mOutlierDistribution.mElapsed += diffMs;
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700219 return isOutlier;
Sanna Catherine de Treville Wager41cad592017-06-29 14:57:59 -0700220}
221
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700222// computes the column width required for a specific histogram value
223inline int numberWidth(int number, int leftPadding) {
224 return std::max(std::max(widthOf(number) + 4, 3), leftPadding + 2);
225}
Sanna Catherine de Treville Wager85768942017-07-26 20:17:30 -0700226
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700227// TODO Make it return a std::string instead of modifying body
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700228// TODO: move this to ReportPerformance, probably make it a friend function of PerformanceAnalysis
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700229void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -0700230 if (mHists.empty()) {
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700231 return;
232 }
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700233
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700234 std::map<int, int> buckets;
Sanna Catherine de Treville Wagera80649a2017-07-21 16:16:38 -0700235 for (const auto &shortHist: mHists) {
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700236 for (const auto &countPair : shortHist.second) {
237 buckets[countPair.first] += countPair.second;
238 }
239 }
240
241 // underscores and spaces length corresponds to maximum width of histogram
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700242 static const int kLen = 100;
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700243 std::string underscores(kLen, '_');
244 std::string spaces(kLen, ' ');
245
246 auto it = buckets.begin();
247 int maxDelta = it->first;
248 int maxCount = it->second;
249 // Compute maximum values
250 while (++it != buckets.end()) {
251 if (it->first > maxDelta) {
252 maxDelta = it->first;
253 }
254 if (it->second > maxCount) {
255 maxCount = it->second;
256 }
257 }
258 int height = log2(maxCount) + 1; // maxCount > 0, safe to call log2
259 const int leftPadding = widthOf(1 << height);
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700260 const int bucketWidth = numberWidth(maxDelta, leftPadding);
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700261 int scalingFactor = 1;
262 // scale data if it exceeds maximum height
263 if (height > maxHeight) {
264 scalingFactor = (height + maxHeight) / maxHeight;
265 height /= scalingFactor;
266 }
Sanna Catherine de Treville Wagere4865262017-07-14 16:24:15 -0700267 // TODO: print reader (author) ID
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700268 body->appendFormat("\n%*s", leftPadding + 11, "Occurrences");
269 // write histogram label line with bucket values
270 body->appendFormat("\n%s", " ");
271 body->appendFormat("%*s", leftPadding, " ");
272 for (auto const &x : buckets) {
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700273 const int colWidth = numberWidth(x.first / kJiffyPerMs, leftPadding);
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700274 body->appendFormat("%*d", colWidth, x.second);
275 }
276 // write histogram ascii art
277 body->appendFormat("\n%s", " ");
278 for (int row = height * scalingFactor; row >= 0; row -= scalingFactor) {
279 const int value = 1 << row;
280 body->appendFormat("%.*s", leftPadding, spaces.c_str());
281 for (auto const &x : buckets) {
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700282 const int colWidth = numberWidth(x.first / kJiffyPerMs, leftPadding);
283 body->appendFormat("%.*s%s", colWidth - 1,
284 spaces.c_str(), x.second < value ? " " : "|");
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700285 }
286 body->appendFormat("\n%s", " ");
287 }
288 // print x-axis
289 const int columns = static_cast<int>(buckets.size());
290 body->appendFormat("%*c", leftPadding, ' ');
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700291 body->appendFormat("%.*s", (columns + 1) * bucketWidth, underscores.c_str());
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700292 body->appendFormat("\n%s", " ");
293
294 // write footer with bucket labels
295 body->appendFormat("%*s", leftPadding, " ");
296 for (auto const &x : buckets) {
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700297 const int colWidth = numberWidth(x.first / kJiffyPerMs, leftPadding);
298 body->appendFormat("%*.*f", colWidth, 1,
299 static_cast<double>(x.first) / kJiffyPerMs);
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700300 }
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700301 body->appendFormat("%.*s%s", bucketWidth, spaces.c_str(), "ms\n");
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700302
Sanna Catherine de Treville Wager316f1fd2017-06-23 09:10:15 -0700303 // Now report glitches
304 body->appendFormat("\ntime elapsed between glitches and glitch timestamps\n");
305 for (const auto &outlier: mOutlierData) {
306 body->appendFormat("%lld: %lld\n", static_cast<long long>(outlier.first),
307 static_cast<long long>(outlier.second));
308 }
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700309}
310
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700311// TODO: learn what timestamp sequences correlate with glitches instead of
312// manually designing a heuristic. Ultimately, detect glitches directly from audio.
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700313// Produces a log warning if the timing of recent buffer periods caused a glitch
314// Computes sum of running window of three buffer periods
315// Checks whether the buffer periods leave enough CPU time for the next one
316// e.g. if a buffer period is expected to be 4 ms and a buffer requires 3 ms of CPU time,
317// here are some glitch cases:
318// 4 + 4 + 6 ; 5 + 4 + 5; 2 + 2 + 10
Sanna Catherine de Treville Wager6ad40ee2017-07-28 10:10:55 -0700319
320// void PerformanceAnalysis::alertIfGlitch(const std::vector<int64_t> &samples) {
321// std::deque<int> periods(kNumBuff, kPeriodMs);
322// for (size_t i = 2; i < samples.size(); ++i) { // skip first time entry
323// periods.push_front(deltaMs(samples[i - 1], samples[i]));
324// periods.pop_back();
325// // TODO: check that all glitch cases are covered
326// if (std::accumulate(periods.begin(), periods.end(), 0) > kNumBuff * kPeriodMs +
327// kPeriodMs - kPeriodMsCPU) {
328// periods.assign(kNumBuff, kPeriodMs);
329// }
330// }
331// return;
332//}
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700333
Sanna Catherine de Treville Wagercf6c75a2017-07-21 17:05:25 -0700334//------------------------------------------------------------------------------
335
336// writes summary of performance into specified file descriptor
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700337void dump(int fd, int indent, PerformanceAnalysisMap &threadPerformanceAnalysis) {
Sanna Catherine de Treville Wagercf6c75a2017-07-21 17:05:25 -0700338 String8 body;
Sanna Catherine de Treville Wagerf8c34282017-07-25 11:31:18 -0700339 const char* const kDirectory = "/data/misc/audioserver/";
Sanna Catherine de Treville Wagercf6c75a2017-07-21 17:05:25 -0700340 for (auto & thread : threadPerformanceAnalysis) {
Sanna Catherine de Treville Wagerd0965172017-07-24 13:42:44 -0700341 for (auto & hash: thread.second) {
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700342 PerformanceAnalysis& curr = hash.second;
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700343 // write performance data to console
344 curr.reportPerformance(&body);
Sanna Catherine de Treville Wager0a3959e2017-07-25 16:08:17 -0700345 if (!body.isEmpty()) {
346 dumpLine(fd, indent, body);
347 body.clear();
348 }
Sanna Catherine de Treville Wager23f89d32017-07-24 18:24:48 -0700349 // write to file
Sanna Catherine de Treville Wagerf8c34282017-07-25 11:31:18 -0700350 writeToFile(curr.mHists, curr.mOutlierData, curr.mPeakTimestamps,
351 kDirectory, false, thread.first, hash.first);
Sanna Catherine de Treville Wagerd0965172017-07-24 13:42:44 -0700352 }
Sanna Catherine de Treville Wagercf6c75a2017-07-21 17:05:25 -0700353 }
Sanna Catherine de Treville Wagercf6c75a2017-07-21 17:05:25 -0700354}
355
Sanna Catherine de Treville Wager85768942017-07-26 20:17:30 -0700356
Sanna Catherine de Treville Wagercf6c75a2017-07-21 17:05:25 -0700357// Writes a string into specified file descriptor
358void dumpLine(int fd, int indent, const String8 &body) {
359 dprintf(fd, "%.*s%s \n", indent, "", body.string());
360}
361
Sanna Catherine de Treville Wager80448082017-07-11 14:07:59 -0700362} // namespace ReportPerformance
363
Sanna Catherine de Treville Wagerd0dfe432017-06-22 15:09:38 -0700364} // namespace android