diff options
author | 2022-09-18 18:02:31 -0700 | |
---|---|---|
committer | 2022-09-18 18:05:03 -0700 | |
commit | 3e4d9f2007cfc866720de4e70668093ca73a1160 (patch) | |
tree | 987e1d1e2db619978f27447201c56978e34b70b3 /libs/androidfw | |
parent | 2aba2a128d4ae666672007d56aea04f034eb05ec (diff) |
Record resource access times
Bug: 246953799
Collect timing information for resource acquisition methods. The
timing has nanosecond resolution and is collected inside the native
runtime code. Timing values are accumulated in the runtime and are
periodically fetched and published by the java framework. This
strategy means that normal resource methods incur a small runtime
overhead. The longer time involved in publishing the values is
handled by an independent Java thread.
A public dumpTimers() method can be called from a dumpsys command, for
debug, but the dumpsys command is not hooked up in this commit.
No APIs are actually monitored in this commit, and the ResourceTimer
loop is not started.
Test: atest
* androidfw_lib
Change-Id: I5995d3c6518ca2fa4c8ab9d3eb610167758c695d
Diffstat (limited to 'libs/androidfw')
-rw-r--r-- | libs/androidfw/Android.bp | 2 | ||||
-rw-r--r-- | libs/androidfw/ResourceTimer.cpp | 271 | ||||
-rw-r--r-- | libs/androidfw/include/androidfw/ResourceTimer.h | 221 | ||||
-rw-r--r-- | libs/androidfw/tests/ResourceTimer_test.cpp | 245 |
4 files changed, 739 insertions, 0 deletions
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 779c4b75efc4..eb8d26adc7d7 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -71,6 +71,7 @@ cc_library { "misc.cpp", "ObbFile.cpp", "PosixUtils.cpp", + "ResourceTimer.cpp", "ResourceTypes.cpp", "ResourceUtils.cpp", "StreamingZipInflater.cpp", @@ -173,6 +174,7 @@ cc_test { "tests/Idmap_test.cpp", "tests/LoadedArsc_test.cpp", "tests/Locale_test.cpp", + "tests/ResourceTimer_test.cpp", "tests/ResourceUtils_test.cpp", "tests/ResTable_test.cpp", "tests/Split_test.cpp", diff --git a/libs/androidfw/ResourceTimer.cpp b/libs/androidfw/ResourceTimer.cpp new file mode 100644 index 000000000000..44128d9e4e3d --- /dev/null +++ b/libs/androidfw/ResourceTimer.cpp @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <unistd.h> +#include <string.h> + +#include <map> +#include <atomic> + +#include <utils/Log.h> +#include <androidfw/ResourceTimer.h> + +// The following block allows compilation on windows, which does not have getuid(). +#ifdef _WIN32 +#ifdef ERROR +#undef ERROR +#endif +#define getuid() (getUidWindows_) +#endif + +namespace android { + +namespace { + +#ifdef _WIN32 +// A temporary to confuse lint into thinking that getuid() on windows might return something other +// than zero. +int getUidWindows_ = 0; +#endif + +// The number of nanoseconds in a microsecond. +static const unsigned int US = 1000; +// The number of nanoseconds in a second. +static const unsigned int S = 1000 * 1000 * 1000; + +// Return the difference between two timespec values. The difference is in nanoseconds. If the +// return value would exceed 2s (2^31 nanoseconds) then UINT_MAX is returned. +unsigned int diffInNs(timespec const &a, timespec const &b) { + timespec r = { 0, 0 }; + r.tv_nsec = a.tv_nsec - b.tv_nsec; + if (r.tv_nsec < 0) { + r.tv_sec = -1; + r.tv_nsec += S; + } + r.tv_sec = r.tv_sec + (a.tv_sec - b.tv_sec); + if (r.tv_sec > 2) return UINT_MAX; + unsigned int result = (r.tv_sec * S) + r.tv_nsec; + if (result > 2 * S) return UINT_MAX; + return result; +} + +} + +ResourceTimer::ResourceTimer(Counter api) + : active_(enabled_.load()), + api_(api) { + if (active_) { + clock_gettime(CLOCK_MONOTONIC, &start_); + } +} + +ResourceTimer::~ResourceTimer() { + record(); +} + +void ResourceTimer::enable() { + if (!enabled_.load()) counter_ = new GuardedTimer[ResourceTimer::counterSize]; + enabled_.store(true); +} + +void ResourceTimer::cancel() { + active_ = false; +} + +void ResourceTimer::record() { + if (!active_) return; + + struct timespec end; + clock_gettime(CLOCK_MONOTONIC, &end); + // Get the difference in microseconds. + const unsigned int ticks = diffInNs(end, start_); + ScopedTimer t(counter_[toIndex(api_)]); + t->record(ticks); + active_ = false; +} + +bool ResourceTimer::copy(int counter, Timer &dst, bool reset) { + ScopedTimer t(counter_[counter]); + if (t->count == 0) { + dst.reset(); + if (reset) t->reset(); + return false; + } + Timer::copy(dst, *t, reset); + return true; +} + +void ResourceTimer::reset() { + for (int i = 0; i < counterSize; i++) { + ScopedTimer t(counter_[i]); + t->reset(); + } +} + +ResourceTimer::Timer::Timer() { + // Ensure newly-created objects are zeroed. + memset(buckets, 0, sizeof(buckets)); + reset(); +} + +ResourceTimer::Timer::~Timer() { + for (int d = 0; d < MaxDimension; d++) { + delete[] buckets[d]; + } +} + +void ResourceTimer::Timer::freeBuckets() { + for (int d = 0; d < MaxDimension; d++) { + delete[] buckets[d]; + buckets[d] = 0; + } +} + +void ResourceTimer::Timer::reset() { + count = total = mintime = maxtime = 0; + memset(largest, 0, sizeof(largest)); + memset(&pvalues, 0, sizeof(pvalues)); + // Zero the histogram, keeping any allocated dimensions. + for (int d = 0; d < MaxDimension; d++) { + if (buckets[d] != 0) memset(buckets[d], 0, sizeof(int) * MaxBuckets); + } +} + +void ResourceTimer::Timer::copy(Timer &dst, Timer &src, bool reset) { + dst.freeBuckets(); + dst = src; + // Clean up the histograms. + if (reset) { + // Do NOT free the src buckets because they being used by dst. + memset(src.buckets, 0, sizeof(src.buckets)); + src.reset(); + } else { + for (int d = 0; d < MaxDimension; d++) { + if (src.buckets[d] != nullptr) { + dst.buckets[d] = new int[MaxBuckets]; + memcpy(dst.buckets[d], src.buckets[d], sizeof(int) * MaxBuckets); + } + } + } +} + +void ResourceTimer::Timer::record(int ticks) { + // Record that the event happened. + count++; + + total += ticks; + if (mintime == 0 || ticks < mintime) mintime = ticks; + if (ticks > maxtime) maxtime = ticks; + + // Do not add oversized events to the histogram. + if (ticks != UINT_MAX) { + for (int d = 0; d < MaxDimension; d++) { + if (ticks < range[d]) { + if (buckets[d] == 0) { + buckets[d] = new int[MaxBuckets]; + memset(buckets[d], 0, sizeof(int) * MaxBuckets); + } + if (ticks < width[d]) { + // Special case: never write to bucket 0 because it complicates the percentile logic. + // However, this is always the smallest possible value to it is very unlikely to ever + // affect any of the percentile results. + buckets[d][1]++; + } else { + buckets[d][ticks / width[d]]++; + } + break; + } + } + } + + // The list of largest times is sorted with the biggest value at index 0 and the smallest at + // index MaxLargest-1. The incoming tick count should be added to the array only if it is + // larger than the current value at MaxLargest-1. + if (ticks > largest[Timer::MaxLargest-1]) { + for (size_t i = 0; i < Timer::MaxLargest; i++) { + if (ticks > largest[i]) { + if (i < Timer::MaxLargest-1) { + for (size_t j = Timer::MaxLargest - 1; j > i; j--) { + largest[j] = largest[j-1]; + } + } + largest[i] = ticks; + break; + } + } + } +} + +void ResourceTimer::Timer::Percentile::compute( + int cumulative, int current, int count, int width, int time) { + nominal = time; + nominal_actual = (cumulative * 100) / count; + floor = nominal - width; + floor_actual = ((cumulative - current) * 100) / count; +} + +void ResourceTimer::Timer::compute() { + memset(&pvalues, 0, sizeof(pvalues)); + + float l50 = count / 2.0; + float l90 = (count * 9.0) / 10.0; + float l95 = (count * 95.0) / 100.0; + float l99 = (count * 99.0) / 100.0; + + int sum = 0; + for (int d = 0; d < MaxDimension; d++) { + if (buckets[d] == 0) continue; + for (int j = 0; j < MaxBuckets && sum < count; j++) { + // Empty buckets don't contribute to the answers. Skip them. + if (buckets[d][j] == 0) continue; + sum += buckets[d][j]; + // A word on indexing. j is never zero in the following lines. buckets[0][0] corresponds + // to a delay of 0us, which cannot happen. buckets[n][0], for n > 0 overlaps a value in + // buckets[n-1], and the code would have stopped there. + if (sum >= l50 && pvalues.p50.nominal == 0) { + pvalues.p50.compute(sum, buckets[d][j], count, width[d], j * width[d]); + } + if (sum >= l90 && pvalues.p90.nominal == 0) { + pvalues.p90.compute(sum, buckets[d][j], count, width[d], j * width[d]); + } + if (sum >= l95 && pvalues.p95.nominal == 0) { + pvalues.p95.compute(sum, buckets[d][j], count, width[d], j * width[d]); + } + if (sum >= l99 && pvalues.p99.nominal == 0) { + pvalues.p99.compute(sum, buckets[d][j], count, width[d], j * width[d]); + } + } + } +} + +char const *ResourceTimer::toString(ResourceTimer::Counter counter) { + switch (counter) { + case Counter::GetResourceValue: + return "GetResourceValue"; + case Counter::RetrieveAttributes: + return "RetrieveAttributes"; + }; + return "Unknown"; +} + +std::atomic<bool> ResourceTimer::enabled_(false); +std::atomic<ResourceTimer::GuardedTimer *> ResourceTimer::counter_(nullptr); + +const int ResourceTimer::Timer::range[] = { 100 * US, 1000 * US, 10*1000 * US, 100*1000 * US }; +const int ResourceTimer::Timer::width[] = { 1 * US, 10 * US, 100 * US, 1000 * US }; + + +} // namespace android diff --git a/libs/androidfw/include/androidfw/ResourceTimer.h b/libs/androidfw/include/androidfw/ResourceTimer.h new file mode 100644 index 000000000000..74613519a920 --- /dev/null +++ b/libs/androidfw/include/androidfw/ResourceTimer.h @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROIDFW_RESOURCETIMER_H_ +#define ANDROIDFW_RESOURCETIMER_H_ + +#include <time.h> +#include <atomic> +#include <vector> + +#include <utils/Mutex.h> +#include <android-base/macros.h> +#include <androidfw/Util.h> + +namespace android { + +// ResourceTimer captures the duration of short functions. Durations are accumulated in registers +// and statistics are pulled back to the Java layer as needed. +// To monitor an API, first add it to the Counter enumeration. Then, inside the API, create an +// instance of ResourceTimer with the appropriate enumeral. The corresponding counter will be +// updated when the ResourceTimer destructor is called, normally at the end of the enclosing block. +class ResourceTimer { + public: + enum class Counter { + GetResourceValue, + RetrieveAttributes, + + LastCounter = RetrieveAttributes, + }; + static const int counterSize = static_cast<int>(Counter::LastCounter) + 1; + static char const *toString(Counter); + + // Start a timer for the specified counter. + ResourceTimer(Counter); + // The block is exiting. If the timer is active, record it. + ~ResourceTimer(); + // This records the elapsed time and disables further recording. Use this if the containing + // block includes extra processing that should not be included in the timer. The method is + // destructive in that the timer is no longer valid and further calls to record() will be + // ignored. + void record(); + // This cancels a timer. Elapsed time will neither be computed nor recorded. + void cancel(); + + // A single timer contains the count of events and the cumulative time spent handling the + // events. It also includes the smallest value seen and 10 largest values seen. Finally, it + // includes a histogram of values that approximates a semi-log. + + // The timer can compute percentiles of recorded events. For example, the p50 value is a time + // such that 50% of the readings are below the value and 50% are above the value. The + // granularity in the readings means that a percentile cannot always be computed. In this case, + // the percentile is reported as zero. (The simplest example is when there is a single + // reading.) Even if the value can be computed, it will not be exact. Therefore, a percentile + // is actually reported as two values: the lowest time at which it might be valid and the + // highest time at which it might be valid. + struct Timer { + static const size_t MaxLargest = 5; + + // The construct zeros all the fields. The destructor releases memory allocated to the + // buckets. + Timer(); + ~Timer(); + + // The following summary values are set to zero on a reset. All times are in ns. + + // The total number of events recorded. + int count; + // The total duration of events. + int64_t total; + // The smallest event duration seen. This is guaranteed to be non-zero if count is greater + // than 0. + int mintime; + // The largest event duration seen. + int maxtime; + + // The largest values seen. Element 0 is the largest value seen (and is the same as maxtime, + // above). Element 1 is the next largest, and so on. If count is less than MaxLargest, + // unused elements will be zero. + int largest[MaxLargest]; + + // The p50 value is a time such that 50% of the readings are below that time and 50% of the + // readings. + + // A single percentile is defined by the lowest value supported by the readings and the + // highest value supported by the readings. + struct Percentile { + // The nominal time (in ns) of the percentile. The true percentile is guaranteed to be less + // than or equal to this time. + int nominal; + // The actual percentile of the nominal time. + int nominal_actual; + // The time of the next lower bin. The true percentile is guaranteed to be greater than + // this time. + int floor; + // The actual percentile of the floor time. + int floor_actual; + + // Fill in a percentile given the cumulative to the bin, the count in the current bin, the + // total count, the width of the bin, and the time of the bin. + void compute(int cumulative, int current, int count, int width, int time); + }; + + // The structure that holds the percentiles. + struct { + Percentile p50; + Percentile p90; + Percentile p95; + Percentile p99; + } pvalues; + + // Set all counters to zero. + void reset(); + // Record an event. The input time is in ns. + void record(int); + // Compute the percentiles. Percentiles are computed on demand, as the computation is too + // expensive to be done inline. + void compute(); + + // Copy one timer to another. If reset is true then the src is reset immediately after the + // copy. The reset flag is exploited to make the copy faster. Any data in dst is lost. + static void copy(Timer &dst, Timer &src, bool reset); + + private: + // Free any buckets. + void freeBuckets(); + + // Readings are placed in bins, which are orgzanized into decades. The decade 0 covers + // [0,100) in steps of 1us. Decade 1 covers [0,1000) in steps of 10us. Decade 2 covers + // [0,10000) in steps of 100us. And so on. + + // An event is placed in the first bin that can hold it. This means that events in the range + // of [0,100) are placed in the first decade, events in the range of [0,1000) are placed in + // the second decade, and so on. This also means that the first 10% of the bins are unused + // in each decade after the first. + + // The design provides at least two significant digits across the range of [0,10000). + + static const size_t MaxDimension = 4; + static const size_t MaxBuckets = 100; + + // The range of each dimension. The lower value is always zero. + static const int range[MaxDimension]; + // The width of each bin, by dimension + static const int width[MaxDimension]; + + // A histogram of the values seen. Centuries are allocated as needed, to minimize the memory + // impact. + int *buckets[MaxDimension]; + }; + + // Fetch one Timer. The function has a short-circuit behavior: if the count is zero then + // destination count is set to zero and the function returns false. Otherwise, the destination + // is a copy of the source and the function returns true. This behavior lowers the cost of + // handling unused timers. + static bool copy(int src, Timer &dst, bool reset); + + // Enable the timers. Timers are initially disabled. Enabling timers allocates memory for the + // counters. Timers cannot be disabled. + static void enable(); + + private: + // An internal reset method. This does not take a lock. + static void reset(); + + // Helper method to convert a counter into an enum. Presumably, this will be inlined into zero + // actual cpu instructions. + static inline std::vector<unsigned int>::size_type toIndex(Counter c) { + return static_cast<std::vector<unsigned int>::size_type>(c); + } + + // Every counter has an associated lock. The lock has been factored into a separate class to + // keep the Timer class a POD. + struct GuardedTimer { + Mutex lock_; + Timer timer_; + }; + + // Scoped timer + struct ScopedTimer { + AutoMutex _l; + Timer &t; + ScopedTimer(GuardedTimer &g) : + _l(g.lock_), t(g.timer_) { + } + Timer *operator->() { + return &t; + } + Timer& operator*() { + return t; + } + }; + + // An individual timer is active (or not), is tracking a specific API, and has a start time. + // The api and the start time are undefined if the timer is not active. + bool active_; + Counter api_; + struct timespec start_; + + // The global enable flag. This is initially false and may be set true by the java runtime. + static std::atomic<bool> enabled_; + + // The global timers. The memory for the timers is not allocated until the timers are enabled. + static std::atomic<GuardedTimer *> counter_; +}; + +} // namespace android + +#endif /* ANDROIDFW_RESOURCETIMER_H_ */ diff --git a/libs/androidfw/tests/ResourceTimer_test.cpp b/libs/androidfw/tests/ResourceTimer_test.cpp new file mode 100644 index 000000000000..4a1e9735de7a --- /dev/null +++ b/libs/androidfw/tests/ResourceTimer_test.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <android-base/file.h> +#include <android-base/test_utils.h> +#include <androidfw/Util.h> + +#include "TestHelpers.h" + +#include <androidfw/ResourceTimer.h> + +namespace android { + +namespace { + +// Create a reading in us. This is a convenience function to avoid multiplying by 1000 +// everywhere. +unsigned int US(int us) { + return us * 1000; +} + +} + +TEST(ResourceTimerTest, TimerBasic) { + ResourceTimer::Timer timer; + ASSERT_THAT(timer.count, 0); + ASSERT_THAT(timer.total, 0); + + for (int i = 1; i <= 100; i++) { + timer.record(US(i)); + } + ASSERT_THAT(timer.count, 100); + ASSERT_THAT(timer.total, US((101 * 100)/2)); + ASSERT_THAT(timer.mintime, US(1)); + ASSERT_THAT(timer.maxtime, US(100)); + ASSERT_THAT(timer.pvalues.p50.floor, 0); + ASSERT_THAT(timer.pvalues.p50.nominal, 0); + ASSERT_THAT(timer.largest[0], US(100)); + ASSERT_THAT(timer.largest[1], US(99)); + ASSERT_THAT(timer.largest[2], US(98)); + ASSERT_THAT(timer.largest[3], US(97)); + ASSERT_THAT(timer.largest[4], US(96)); + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(49)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(50)); + ASSERT_THAT(timer.pvalues.p90.floor, US(89)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(90)); + ASSERT_THAT(timer.pvalues.p95.floor, US(94)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(95)); + ASSERT_THAT(timer.pvalues.p99.floor, US(98)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(99)); + + // Test reset functionality. All values should be zero after the reset. Computing pvalues + // after the result should also yield zeros. + timer.reset(); + ASSERT_THAT(timer.count, 0); + ASSERT_THAT(timer.total, 0); + ASSERT_THAT(timer.mintime, US(0)); + ASSERT_THAT(timer.maxtime, US(0)); + ASSERT_THAT(timer.pvalues.p50.floor, US(0)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(0)); + ASSERT_THAT(timer.largest[0], US(0)); + ASSERT_THAT(timer.largest[1], US(0)); + ASSERT_THAT(timer.largest[2], US(0)); + ASSERT_THAT(timer.largest[3], US(0)); + ASSERT_THAT(timer.largest[4], US(0)); + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(0)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(0)); + ASSERT_THAT(timer.pvalues.p90.floor, US(0)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(0)); + ASSERT_THAT(timer.pvalues.p95.floor, US(0)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(0)); + ASSERT_THAT(timer.pvalues.p99.floor, US(0)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(0)); + + // Test again, adding elements in reverse. + for (int i = 100; i >= 1; i--) { + timer.record(US(i)); + } + ASSERT_THAT(timer.count, 100); + ASSERT_THAT(timer.total, US((101 * 100)/2)); + ASSERT_THAT(timer.mintime, US(1)); + ASSERT_THAT(timer.maxtime, US(100)); + ASSERT_THAT(timer.pvalues.p50.floor, 0); + ASSERT_THAT(timer.pvalues.p50.nominal, 0); + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(49)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(50)); + ASSERT_THAT(timer.pvalues.p90.floor, US(89)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(90)); + ASSERT_THAT(timer.pvalues.p95.floor, US(94)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(95)); + ASSERT_THAT(timer.pvalues.p99.floor, US(98)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(99)); + ASSERT_THAT(timer.largest[0], US(100)); + ASSERT_THAT(timer.largest[1], US(99)); + ASSERT_THAT(timer.largest[2], US(98)); + ASSERT_THAT(timer.largest[3], US(97)); + ASSERT_THAT(timer.largest[4], US(96)); +} + +TEST(ResourceTimerTest, TimerLimit) { + ResourceTimer::Timer timer; + + // Event truncation means that a time of 1050us will be stored in the 1000us + // bucket. Since there is a single event, all p-values lie in the same range. + timer.record(US(1050)); + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(900)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(1000)); + ASSERT_THAT(timer.pvalues.p90.floor, US(900)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(1000)); + ASSERT_THAT(timer.pvalues.p95.floor, US(900)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(1000)); + ASSERT_THAT(timer.pvalues.p99.floor, US(900)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(1000)); +} + +TEST(ResourceTimerTest, TimerCopy) { + ResourceTimer::Timer source; + for (int i = 1; i <= 100; i++) { + source.record(US(i)); + } + ResourceTimer::Timer timer; + ResourceTimer::Timer::copy(timer, source, true); + ASSERT_THAT(source.count, 0); + ASSERT_THAT(source.total, 0); + // compute() is not normally be called on a reset timer, but it should work and it should return + // all zeros. + source.compute(); + ASSERT_THAT(source.pvalues.p50.floor, US(0)); + ASSERT_THAT(source.pvalues.p50.nominal, US(0)); + ASSERT_THAT(source.pvalues.p90.floor, US(0)); + ASSERT_THAT(source.pvalues.p90.nominal, US(0)); + ASSERT_THAT(source.pvalues.p95.floor, US(0)); + ASSERT_THAT(source.pvalues.p95.nominal, US(0)); + ASSERT_THAT(source.pvalues.p99.floor, US(0)); + ASSERT_THAT(source.pvalues.p99.nominal, US(0)); + ASSERT_THAT(source.largest[0], US(0)); + ASSERT_THAT(source.largest[1], US(0)); + ASSERT_THAT(source.largest[2], US(0)); + ASSERT_THAT(source.largest[3], US(0)); + ASSERT_THAT(source.largest[4], US(0)); + + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(49)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(50)); + ASSERT_THAT(timer.pvalues.p90.floor, US(89)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(90)); + ASSERT_THAT(timer.pvalues.p95.floor, US(94)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(95)); + ASSERT_THAT(timer.pvalues.p99.floor, US(98)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(99)); + ASSERT_THAT(timer.largest[0], US(100)); + ASSERT_THAT(timer.largest[1], US(99)); + ASSERT_THAT(timer.largest[2], US(98)); + ASSERT_THAT(timer.largest[3], US(97)); + ASSERT_THAT(timer.largest[4], US(96)); + + // Call compute a second time. The values must be the same. + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(49)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(50)); + ASSERT_THAT(timer.pvalues.p90.floor, US(89)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(90)); + ASSERT_THAT(timer.pvalues.p95.floor, US(94)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(95)); + ASSERT_THAT(timer.pvalues.p99.floor, US(98)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(99)); + ASSERT_THAT(timer.largest[0], US(100)); + ASSERT_THAT(timer.largest[1], US(99)); + ASSERT_THAT(timer.largest[2], US(98)); + ASSERT_THAT(timer.largest[3], US(97)); + ASSERT_THAT(timer.largest[4], US(96)); + + // Modify the source. If timer and source share histogram arrays, this will introduce an + // error. + for (int i = 1; i <= 100; i++) { + source.record(US(i)); + } + // Call compute a third time. The values must be the same. + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(49)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(50)); + ASSERT_THAT(timer.pvalues.p90.floor, US(89)); + ASSERT_THAT(timer.pvalues.p90.nominal, US(90)); + ASSERT_THAT(timer.pvalues.p95.floor, US(94)); + ASSERT_THAT(timer.pvalues.p95.nominal, US(95)); + ASSERT_THAT(timer.pvalues.p99.floor, US(98)); + ASSERT_THAT(timer.pvalues.p99.nominal, US(99)); + ASSERT_THAT(timer.largest[0], US(100)); + ASSERT_THAT(timer.largest[1], US(99)); + ASSERT_THAT(timer.largest[2], US(98)); + ASSERT_THAT(timer.largest[3], US(97)); + ASSERT_THAT(timer.largest[4], US(96)); +} + +// Verify that if too many oversize entries are reported, the percentile values cannot be computed +// and are set to zero. +TEST(ResourceTimerTest, TimerOversize) { + static const int oversize = US(2 * 1000 * 1000); + + ResourceTimer::Timer timer; + for (int i = 1; i <= 100; i++) { + timer.record(US(i)); + } + + // Insert enough oversize values to invalidate the p90, p95, and p99 percentiles. The p50 is + // still computable. + for (int i = 1; i <= 50; i++) { + timer.record(oversize); + } + ASSERT_THAT(timer.largest[0], oversize); + ASSERT_THAT(timer.largest[1], oversize); + ASSERT_THAT(timer.largest[2], oversize); + ASSERT_THAT(timer.largest[3], oversize); + ASSERT_THAT(timer.largest[4], oversize); + timer.compute(); + ASSERT_THAT(timer.pvalues.p50.floor, US(74)); + ASSERT_THAT(timer.pvalues.p50.nominal, US(75)); + ASSERT_THAT(timer.pvalues.p90.floor, 0); + ASSERT_THAT(timer.pvalues.p90.nominal, 0); + ASSERT_THAT(timer.pvalues.p95.floor, 0); + ASSERT_THAT(timer.pvalues.p95.nominal, 0); + ASSERT_THAT(timer.pvalues.p99.floor, 0); + ASSERT_THAT(timer.pvalues.p99.nominal, 0); +} + + +} // namespace android |