1 /*
2 * Copyright (C) 2022 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 #include <unistd.h>
18 #include <string.h>
19
20 #include <map>
21 #include <atomic>
22
23 #include <utils/Log.h>
24 #include <androidfw/ResourceTimer.h>
25
26 // The following block allows compilation on windows, which does not have getuid().
27 #ifdef _WIN32
28 #ifdef ERROR
29 #undef ERROR
30 #endif
31 #define getuid() (getUidWindows_)
32 #endif
33
34 namespace android {
35
36 namespace {
37
38 #ifdef _WIN32
39 // A temporary to confuse lint into thinking that getuid() on windows might return something other
40 // than zero.
41 int getUidWindows_ = 0;
42 #endif
43
44 // The number of nanoseconds in a microsecond.
45 static const unsigned int US = 1000;
46 // The number of nanoseconds in a second.
47 static const unsigned int S = 1000 * 1000 * 1000;
48
49 // Return the difference between two timespec values. The difference is in nanoseconds. If the
50 // return value would exceed 2s (2^31 nanoseconds) then UINT_MAX is returned.
diffInNs(timespec const & a,timespec const & b)51 unsigned int diffInNs(timespec const &a, timespec const &b) {
52 timespec r = { 0, 0 };
53 r.tv_nsec = a.tv_nsec - b.tv_nsec;
54 if (r.tv_nsec < 0) {
55 r.tv_sec = -1;
56 r.tv_nsec += S;
57 }
58 r.tv_sec = r.tv_sec + (a.tv_sec - b.tv_sec);
59 if (r.tv_sec > 2) return UINT_MAX;
60 unsigned int result = (r.tv_sec * S) + r.tv_nsec;
61 if (result > 2 * S) return UINT_MAX;
62 return result;
63 }
64
65 }
66
ResourceTimer(Counter api)67 ResourceTimer::ResourceTimer(Counter api)
68 : active_(enabled_.load()),
69 api_(api) {
70 if (active_) {
71 clock_gettime(CLOCK_MONOTONIC, &start_);
72 }
73 }
74
~ResourceTimer()75 ResourceTimer::~ResourceTimer() {
76 record();
77 }
78
enable()79 void ResourceTimer::enable() {
80 if (!enabled_.load()) counter_ = new GuardedTimer[ResourceTimer::counterSize];
81 enabled_.store(true);
82 }
83
cancel()84 void ResourceTimer::cancel() {
85 active_ = false;
86 }
87
record()88 void ResourceTimer::record() {
89 if (!active_) return;
90
91 struct timespec end;
92 clock_gettime(CLOCK_MONOTONIC, &end);
93 // Get the difference in microseconds.
94 const unsigned int ticks = diffInNs(end, start_);
95 ScopedTimer t(counter_[toIndex(api_)]);
96 t->record(ticks);
97 active_ = false;
98 }
99
copy(int counter,Timer & dst,bool reset)100 bool ResourceTimer::copy(int counter, Timer &dst, bool reset) {
101 ScopedTimer t(counter_[counter]);
102 if (t->count == 0) {
103 dst.reset();
104 if (reset) t->reset();
105 return false;
106 }
107 Timer::copy(dst, *t, reset);
108 return true;
109 }
110
reset()111 void ResourceTimer::reset() {
112 for (int i = 0; i < counterSize; i++) {
113 ScopedTimer t(counter_[i]);
114 t->reset();
115 }
116 }
117
Timer()118 ResourceTimer::Timer::Timer() {
119 // Ensure newly-created objects are zeroed.
120 memset(buckets, 0, sizeof(buckets));
121 reset();
122 }
123
~Timer()124 ResourceTimer::Timer::~Timer() {
125 for (int d = 0; d < MaxDimension; d++) {
126 delete[] buckets[d];
127 }
128 }
129
freeBuckets()130 void ResourceTimer::Timer::freeBuckets() {
131 for (int d = 0; d < MaxDimension; d++) {
132 delete[] buckets[d];
133 buckets[d] = 0;
134 }
135 }
136
reset()137 void ResourceTimer::Timer::reset() {
138 count = total = mintime = maxtime = 0;
139 memset(largest, 0, sizeof(largest));
140 memset(&pvalues, 0, sizeof(pvalues));
141 // Zero the histogram, keeping any allocated dimensions.
142 for (int d = 0; d < MaxDimension; d++) {
143 if (buckets[d] != 0) memset(buckets[d], 0, sizeof(int) * MaxBuckets);
144 }
145 }
146
copy(Timer & dst,Timer & src,bool reset)147 void ResourceTimer::Timer::copy(Timer &dst, Timer &src, bool reset) {
148 dst.freeBuckets();
149 dst = src;
150 // Clean up the histograms.
151 if (reset) {
152 // Do NOT free the src buckets because they being used by dst.
153 memset(src.buckets, 0, sizeof(src.buckets));
154 src.reset();
155 } else {
156 for (int d = 0; d < MaxDimension; d++) {
157 if (src.buckets[d] != nullptr) {
158 dst.buckets[d] = new int[MaxBuckets];
159 memcpy(dst.buckets[d], src.buckets[d], sizeof(int) * MaxBuckets);
160 }
161 }
162 }
163 }
164
record(int ticks)165 void ResourceTimer::Timer::record(int ticks) {
166 // Record that the event happened.
167 count++;
168
169 total += ticks;
170 if (mintime == 0 || ticks < mintime) mintime = ticks;
171 if (ticks > maxtime) maxtime = ticks;
172
173 // Do not add oversized events to the histogram.
174 if (ticks != UINT_MAX) {
175 for (int d = 0; d < MaxDimension; d++) {
176 if (ticks < range[d]) {
177 if (buckets[d] == 0) {
178 buckets[d] = new int[MaxBuckets];
179 memset(buckets[d], 0, sizeof(int) * MaxBuckets);
180 }
181 if (ticks < width[d]) {
182 // Special case: never write to bucket 0 because it complicates the percentile logic.
183 // However, this is always the smallest possible value to it is very unlikely to ever
184 // affect any of the percentile results.
185 buckets[d][1]++;
186 } else {
187 buckets[d][ticks / width[d]]++;
188 }
189 break;
190 }
191 }
192 }
193
194 // The list of largest times is sorted with the biggest value at index 0 and the smallest at
195 // index MaxLargest-1. The incoming tick count should be added to the array only if it is
196 // larger than the current value at MaxLargest-1.
197 if (ticks > largest[Timer::MaxLargest-1]) {
198 for (size_t i = 0; i < Timer::MaxLargest; i++) {
199 if (ticks > largest[i]) {
200 if (i < Timer::MaxLargest-1) {
201 for (size_t j = Timer::MaxLargest - 1; j > i; j--) {
202 largest[j] = largest[j-1];
203 }
204 }
205 largest[i] = ticks;
206 break;
207 }
208 }
209 }
210 }
211
compute(int cumulative,int current,int count,int width,int time)212 void ResourceTimer::Timer::Percentile::compute(
213 int cumulative, int current, int count, int width, int time) {
214 nominal = time;
215 nominal_actual = (cumulative * 100) / count;
216 floor = nominal - width;
217 floor_actual = ((cumulative - current) * 100) / count;
218 }
219
compute()220 void ResourceTimer::Timer::compute() {
221 memset(&pvalues, 0, sizeof(pvalues));
222
223 float l50 = count / 2.0;
224 float l90 = (count * 9.0) / 10.0;
225 float l95 = (count * 95.0) / 100.0;
226 float l99 = (count * 99.0) / 100.0;
227
228 int sum = 0;
229 for (int d = 0; d < MaxDimension; d++) {
230 if (buckets[d] == 0) continue;
231 for (int j = 0; j < MaxBuckets && sum < count; j++) {
232 // Empty buckets don't contribute to the answers. Skip them.
233 if (buckets[d][j] == 0) continue;
234 sum += buckets[d][j];
235 // A word on indexing. j is never zero in the following lines. buckets[0][0] corresponds
236 // to a delay of 0us, which cannot happen. buckets[n][0], for n > 0 overlaps a value in
237 // buckets[n-1], and the code would have stopped there.
238 if (sum >= l50 && pvalues.p50.nominal == 0) {
239 pvalues.p50.compute(sum, buckets[d][j], count, width[d], j * width[d]);
240 }
241 if (sum >= l90 && pvalues.p90.nominal == 0) {
242 pvalues.p90.compute(sum, buckets[d][j], count, width[d], j * width[d]);
243 }
244 if (sum >= l95 && pvalues.p95.nominal == 0) {
245 pvalues.p95.compute(sum, buckets[d][j], count, width[d], j * width[d]);
246 }
247 if (sum >= l99 && pvalues.p99.nominal == 0) {
248 pvalues.p99.compute(sum, buckets[d][j], count, width[d], j * width[d]);
249 }
250 }
251 }
252 }
253
toString(ResourceTimer::Counter counter)254 char const *ResourceTimer::toString(ResourceTimer::Counter counter) {
255 switch (counter) {
256 case Counter::GetResourceValue:
257 return "GetResourceValue";
258 case Counter::RetrieveAttributes:
259 return "RetrieveAttributes";
260 };
261 return "Unknown";
262 }
263
264 std::atomic<bool> ResourceTimer::enabled_(false);
265 std::atomic<ResourceTimer::GuardedTimer *> ResourceTimer::counter_(nullptr);
266
267 const int ResourceTimer::Timer::range[] = { 100 * US, 1000 * US, 10*1000 * US, 100*1000 * US };
268 const int ResourceTimer::Timer::width[] = { 1 * US, 10 * US, 100 * US, 1000 * US };
269
270
271 } // namespace android
272