diff options
author | 2017-07-05 14:03:43 -0700 | |
---|---|---|
committer | 2017-07-05 14:04:51 -0700 | |
commit | 7075c79209256101aee60584ee7e1d6f7f959c61 (patch) | |
tree | 4592cb397ed61aa5e98e5ab01cbe646418bd93c8 | |
parent | 6f76e7f96d34215dcff29982e65d2f642e6578aa (diff) |
Split out jank data from policy
Move ProfileData out to its own file with helper
accessors. This keeps policy (what is/isn't jank)
outside of the data storage.
Also use lambdas to iterate over the histogram
to make it nicer for dumping & proto-ifying.
Test: hwui_unit_tests pass & jank data still dumps
Change-Id: I88488369ec77590a2867f51128e65bb786aa34e6
-rw-r--r-- | libs/hwui/Android.bp | 1 | ||||
-rw-r--r-- | libs/hwui/JankTracker.cpp | 152 | ||||
-rw-r--r-- | libs/hwui/JankTracker.h | 31 | ||||
-rw-r--r-- | libs/hwui/ProfileData.cpp | 177 | ||||
-rw-r--r-- | libs/hwui/ProfileData.h | 109 | ||||
-rw-r--r-- | libs/hwui/service/GraphicsStatsService.cpp | 49 | ||||
-rw-r--r-- | libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp | 64 |
7 files changed, 345 insertions, 238 deletions
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 303d05f084aa..92d53da602b7 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -201,6 +201,7 @@ cc_defaults { "PathParser.cpp", "PathTessellator.cpp", "PixelBuffer.cpp", + "ProfileData.cpp", "ProfileRenderer.cpp", "Program.cpp", "ProgramCache.cpp", diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index 028d9f756fb7..f9671ed43771 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -34,14 +34,6 @@ namespace android { namespace uirenderer { -static const char* JANK_TYPE_NAMES[] = { - "Missed Vsync", - "High input latency", - "Slow UI thread", - "Slow bitmap uploads", - "Slow issue draw commands", -}; - struct Comparison { FrameInfoIndex start; FrameInfoIndex end; @@ -68,65 +60,11 @@ static const int64_t IGNORE_EXCEEDING = seconds_to_nanoseconds(10); */ static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::SurfaceCanvas; -// The bucketing algorithm controls so to speak -// If a frame is <= to this it goes in bucket 0 -static const uint32_t kBucketMinThreshold = 5; -// If a frame is > this, start counting in increments of 2ms -static const uint32_t kBucket2msIntervals = 32; -// If a frame is > this, start counting in increments of 4ms -static const uint32_t kBucket4msIntervals = 48; - // For testing purposes to try and eliminate test infra overhead we will // consider any unknown delay of frame start as part of the test infrastructure // and filter it out of the frame profile data static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync; -// The interval of the slow frame histogram -static const uint32_t kSlowFrameBucketIntervalMs = 50; -// The start point of the slow frame bucket in ms -static const uint32_t kSlowFrameBucketStartMs = 150; - -// This will be called every frame, performance sensitive -// Uses bit twiddling to avoid branching while achieving the packing desired -static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime) { - uint32_t index = static_cast<uint32_t>(ns2ms(frameTime)); - // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result - // of negating 1 (twos compliment, yaay) else mask will be 0 - uint32_t mask = -(index > kBucketMinThreshold); - // If index > threshold, this will essentially perform: - // amountAboveThreshold = index - threshold; - // index = threshold + (amountAboveThreshold / 2) - // However if index is <= this will do nothing. It will underflow, do - // a right shift by 0 (no-op), then overflow back to the original value - index = ((index - kBucket4msIntervals) >> (index > kBucket4msIntervals)) - + kBucket4msIntervals; - index = ((index - kBucket2msIntervals) >> (index > kBucket2msIntervals)) - + kBucket2msIntervals; - // If index was < minThreshold at the start of all this it's going to - // be a pretty garbage value right now. However, mask is 0 so we'll end - // up with the desired result of 0. - index = (index - kBucketMinThreshold) & mask; - return index; -} - -// Only called when dumping stats, less performance sensitive -int32_t JankTracker::frameTimeForFrameCountIndex(uint32_t index) { - index = index + kBucketMinThreshold; - if (index > kBucket2msIntervals) { - index += (index - kBucket2msIntervals); - } - if (index > kBucket4msIntervals) { - // This works because it was already doubled by the above if - // 1 is added to shift slightly more towards the middle of the bucket - index += (index - kBucket4msIntervals) + 1; - } - return index; -} - -int32_t JankTracker::frameTimeForSlowFrameCountIndex(uint32_t index) { - return (index * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs; -} - JankTracker::JankTracker(const DisplayInfo& displayInfo) { // By default this will use malloc memory. It may be moved later to ashmem // if there is shared space for it and a request comes in to do that. @@ -199,29 +137,7 @@ void JankTracker::switchStorageToAshmem(int ashmemfd) { return; } - // The new buffer may have historical data that we want to build on top of - // But let's make sure we don't overflow Just In Case - uint32_t divider = 0; - if (newData->totalFrameCount > (1 << 24)) { - divider = 4; - } - for (size_t i = 0; i < mData->jankTypeCounts.size(); i++) { - newData->jankTypeCounts[i] >>= divider; - newData->jankTypeCounts[i] += mData->jankTypeCounts[i]; - } - for (size_t i = 0; i < mData->frameCounts.size(); i++) { - newData->frameCounts[i] >>= divider; - newData->frameCounts[i] += mData->frameCounts[i]; - } - newData->jankFrameCount >>= divider; - newData->jankFrameCount += mData->jankFrameCount; - newData->totalFrameCount >>= divider; - newData->totalFrameCount += mData->totalFrameCount; - if (newData->statStartTime > mData->statStartTime - || newData->statStartTime == 0) { - newData->statStartTime = mData->statStartTime; - } - + newData->mergeWith(*mData); freeData(); mData = newData; mIsMapped = true; @@ -251,7 +167,6 @@ void JankTracker::setFrameInterval(nsecs_t frameInterval) { } void JankTracker::addFrame(const FrameInfo& frame) { - mData->totalFrameCount++; // Fast-path for jank-free frames int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted); if (mDequeueTimeForgiveness @@ -271,11 +186,10 @@ void JankTracker::addFrame(const FrameInfo& frame) { } } LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64, totalDuration); - uint32_t framebucket = frameCountIndexForFrameTime(totalDuration); - LOG_ALWAYS_FATAL_IF(framebucket < 0, "framebucket < 0 (%u)", framebucket); + mData->reportFrame(totalDuration); + // Keep the fast path as fast as possible. if (CC_LIKELY(totalDuration < mFrameInterval)) { - mData->frameCounts[framebucket]++; return; } @@ -284,22 +198,12 @@ void JankTracker::addFrame(const FrameInfo& frame) { return; } - if (framebucket <= mData->frameCounts.size()) { - mData->frameCounts[framebucket]++; - } else { - framebucket = (ns2ms(totalDuration) - kSlowFrameBucketStartMs) - / kSlowFrameBucketIntervalMs; - framebucket = std::min(framebucket, - static_cast<uint32_t>(mData->slowFrameCounts.size() - 1)); - mData->slowFrameCounts[framebucket]++; - } - - mData->jankFrameCount++; + mData->reportJank(); for (int i = 0; i < NUM_BUCKETS; i++) { int64_t delta = frame.duration(COMPARISONS[i].start, COMPARISONS[i].end); if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) { - mData->jankTypeCounts[i]++; + mData->reportJankType((JankType) i); } } } @@ -320,58 +224,16 @@ void JankTracker::dumpData(int fd, const ProfileDataDescription* description, co if (sFrameStart != FrameInfoIndex::IntendedVsync) { dprintf(fd, "\nNote: Data has been filtered!"); } - dprintf(fd, "\nStats since: %" PRIu64 "ns", data->statStartTime); - dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount); - dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount, - (float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f); - dprintf(fd, "\n50th percentile: %ums", findPercentile(data, 50)); - dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90)); - dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95)); - dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99)); - for (int i = 0; i < NUM_BUCKETS; i++) { - dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]); - } - dprintf(fd, "\nHISTOGRAM:"); - for (size_t i = 0; i < data->frameCounts.size(); i++) { - dprintf(fd, " %ums=%u", frameTimeForFrameCountIndex(i), - data->frameCounts[i]); - } - for (size_t i = 0; i < data->slowFrameCounts.size(); i++) { - dprintf(fd, " %ums=%u", frameTimeForSlowFrameCountIndex(i), - data->slowFrameCounts[i]); - } + data->dump(fd); dprintf(fd, "\n"); } void JankTracker::reset() { - mData->jankTypeCounts.fill(0); - mData->frameCounts.fill(0); - mData->slowFrameCounts.fill(0); - mData->totalFrameCount = 0; - mData->jankFrameCount = 0; - mData->statStartTime = systemTime(CLOCK_MONOTONIC); + mData->reset(); sFrameStart = Properties::filterOutTestOverhead ? FrameInfoIndex::HandleInputStart : FrameInfoIndex::IntendedVsync; } -uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) { - int pos = percentile * data->totalFrameCount / 100; - int remaining = data->totalFrameCount - pos; - for (int i = data->slowFrameCounts.size() - 1; i >= 0; i--) { - remaining -= data->slowFrameCounts[i]; - if (remaining <= 0) { - return (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs; - } - } - for (int i = data->frameCounts.size() - 1; i >= 0; i--) { - remaining -= data->frameCounts[i]; - if (remaining <= 0) { - return frameTimeForFrameCountIndex(i); - } - } - return 0; -} - } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h index 6ff5d890eaf7..2c567b361a15 100644 --- a/libs/hwui/JankTracker.h +++ b/libs/hwui/JankTracker.h @@ -17,6 +17,7 @@ #define JANKTRACKER_H_ #include "FrameInfo.h" +#include "ProfileData.h" #include "renderthread/TimeLord.h" #include "utils/RingBuffer.h" @@ -29,31 +30,6 @@ namespace android { namespace uirenderer { -enum JankType { - kMissedVsync = 0, - kHighInputLatency, - kSlowUI, - kSlowSync, - kSlowRT, - - // must be last - NUM_BUCKETS, -}; - -// Try to keep as small as possible, should match ASHMEM_SIZE in -// GraphicsStatsService.java -struct ProfileData { - std::array<uint32_t, NUM_BUCKETS> jankTypeCounts; - // See comments on kBucket* constants for what this holds - std::array<uint32_t, 57> frameCounts; - // Holds a histogram of frame times in 50ms increments from 150ms to 5s - std::array<uint16_t, 97> slowFrameCounts; - - uint32_t totalFrameCount; - uint32_t jankFrameCount; - nsecs_t statStartTime; -}; - enum class JankTrackerType { // The default, means there's no description set Generic, @@ -88,15 +64,12 @@ public: void rotateStorage(); void switchStorageToAshmem(int ashmemfd); - uint32_t findPercentile(int p) { return findPercentile(mData, p); } - static int32_t frameTimeForFrameCountIndex(uint32_t index); - static int32_t frameTimeForSlowFrameCountIndex(uint32_t index); + uint32_t findPercentile(int p) { return mData->findPercentile(p); } private: void freeData(); void setFrameInterval(nsecs_t frameIntervalNanos); - static uint32_t findPercentile(const ProfileData* data, int p); static void dumpData(int fd, const ProfileDataDescription* description, const ProfileData* data); std::array<int64_t, NUM_BUCKETS> mThresholds; diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp new file mode 100644 index 000000000000..a295c5debc67 --- /dev/null +++ b/libs/hwui/ProfileData.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2017 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 "ProfileData.h" + +#include <cinttypes> + +namespace android { +namespace uirenderer { + +static const char* JANK_TYPE_NAMES[] = { + "Missed Vsync", + "High input latency", + "Slow UI thread", + "Slow bitmap uploads", + "Slow issue draw commands", +}; + +// The bucketing algorithm controls so to speak +// If a frame is <= to this it goes in bucket 0 +static const uint32_t kBucketMinThreshold = 5; +// If a frame is > this, start counting in increments of 2ms +static const uint32_t kBucket2msIntervals = 32; +// If a frame is > this, start counting in increments of 4ms +static const uint32_t kBucket4msIntervals = 48; + +// The interval of the slow frame histogram +static const uint32_t kSlowFrameBucketIntervalMs = 50; +// The start point of the slow frame bucket in ms +static const uint32_t kSlowFrameBucketStartMs = 150; + +// This will be called every frame, performance sensitive +// Uses bit twiddling to avoid branching while achieving the packing desired +static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime) { + uint32_t index = static_cast<uint32_t>(ns2ms(frameTime)); + // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result + // of negating 1 (twos compliment, yaay) else mask will be 0 + uint32_t mask = -(index > kBucketMinThreshold); + // If index > threshold, this will essentially perform: + // amountAboveThreshold = index - threshold; + // index = threshold + (amountAboveThreshold / 2) + // However if index is <= this will do nothing. It will underflow, do + // a right shift by 0 (no-op), then overflow back to the original value + index = ((index - kBucket4msIntervals) >> (index > kBucket4msIntervals)) + + kBucket4msIntervals; + index = ((index - kBucket2msIntervals) >> (index > kBucket2msIntervals)) + + kBucket2msIntervals; + // If index was < minThreshold at the start of all this it's going to + // be a pretty garbage value right now. However, mask is 0 so we'll end + // up with the desired result of 0. + index = (index - kBucketMinThreshold) & mask; + return index; +} + +// Only called when dumping stats, less performance sensitive +uint32_t ProfileData::frameTimeForFrameCountIndex(uint32_t index) { + index = index + kBucketMinThreshold; + if (index > kBucket2msIntervals) { + index += (index - kBucket2msIntervals); + } + if (index > kBucket4msIntervals) { + // This works because it was already doubled by the above if + // 1 is added to shift slightly more towards the middle of the bucket + index += (index - kBucket4msIntervals) + 1; + } + return index; +} + +uint32_t ProfileData::frameTimeForSlowFrameCountIndex(uint32_t index) { + return (index * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs; +} + +void ProfileData::mergeWith(const ProfileData& other) { + // Make sure we don't overflow Just In Case + uint32_t divider = 0; + if (mTotalFrameCount > (1 << 24)) { + divider = 4; + } + for (size_t i = 0; i < other.mJankTypeCounts.size(); i++) { + mJankTypeCounts[i] >>= divider; + mJankTypeCounts[i] += other.mJankTypeCounts[i]; + } + for (size_t i = 0; i < other.mFrameCounts.size(); i++) { + mFrameCounts[i] >>= divider; + mFrameCounts[i] += other.mFrameCounts[i]; + } + mJankFrameCount >>= divider; + mJankFrameCount += other.mJankFrameCount; + mTotalFrameCount >>= divider; + mTotalFrameCount += other.mTotalFrameCount; + if (mStatStartTime > other.mStatStartTime + || mStatStartTime == 0) { + mStatStartTime = other.mStatStartTime; + } +} + +void ProfileData::dump(int fd) const { + dprintf(fd, "\nStats since: %" PRIu64 "ns", mStatStartTime); + dprintf(fd, "\nTotal frames rendered: %u", mTotalFrameCount); + dprintf(fd, "\nJanky frames: %u (%.2f%%)", mJankFrameCount, + (float) mJankFrameCount / (float) mTotalFrameCount * 100.0f); + dprintf(fd, "\n50th percentile: %ums", findPercentile(50)); + dprintf(fd, "\n90th percentile: %ums", findPercentile(90)); + dprintf(fd, "\n95th percentile: %ums", findPercentile(95)); + dprintf(fd, "\n99th percentile: %ums", findPercentile(99)); + for (int i = 0; i < NUM_BUCKETS; i++) { + dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], mJankTypeCounts[i]); + } + dprintf(fd, "\nHISTOGRAM:"); + histogramForEach([fd](HistogramEntry entry) { + dprintf(fd, " %ums=%u", entry.renderTimeMs, entry.frameCount); + }); +} + +uint32_t ProfileData::findPercentile(int percentile) const { + int pos = percentile * mTotalFrameCount / 100; + int remaining = mTotalFrameCount - pos; + for (int i = mSlowFrameCounts.size() - 1; i >= 0; i--) { + remaining -= mSlowFrameCounts[i]; + if (remaining <= 0) { + return (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs; + } + } + for (int i = mFrameCounts.size() - 1; i >= 0; i--) { + remaining -= mFrameCounts[i]; + if (remaining <= 0) { + return frameTimeForFrameCountIndex(i); + } + } + return 0; +} + +void ProfileData::reset() { + mJankTypeCounts.fill(0); + mFrameCounts.fill(0); + mSlowFrameCounts.fill(0); + mTotalFrameCount = 0; + mJankFrameCount = 0; + mStatStartTime = systemTime(CLOCK_MONOTONIC); +} + +void ProfileData::reportFrame(int64_t duration) { + mTotalFrameCount++; + uint32_t framebucket = frameCountIndexForFrameTime(duration); + if (framebucket <= mFrameCounts.size()) { + mFrameCounts[framebucket]++; + } else { + framebucket = (ns2ms(duration) - kSlowFrameBucketStartMs) / kSlowFrameBucketIntervalMs; + framebucket = std::min(framebucket, static_cast<uint32_t>(mSlowFrameCounts.size() - 1)); + mSlowFrameCounts[framebucket]++; + } +} + +void ProfileData::histogramForEach(const std::function<void(HistogramEntry)>& callback) const { + for (size_t i = 0; i < mFrameCounts.size(); i++) { + callback(HistogramEntry{frameTimeForFrameCountIndex(i), mFrameCounts[i]}); + } + for (size_t i = 0; i < mSlowFrameCounts.size(); i++) { + callback(HistogramEntry{frameTimeForSlowFrameCountIndex(i), mSlowFrameCounts[i]}); + } +} + +} /* namespace uirenderer */ +} /* namespace android */
\ No newline at end of file diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h new file mode 100644 index 000000000000..d53ee29511f0 --- /dev/null +++ b/libs/hwui/ProfileData.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 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. + */ + +#pragma once + +#include "utils/Macros.h" + +#include <utils/Timers.h> + +#include <array> +#include <functional> +#include <tuple> + +namespace android { +namespace uirenderer { + +enum JankType { + kMissedVsync = 0, + kHighInputLatency, + kSlowUI, + kSlowSync, + kSlowRT, + + // must be last + NUM_BUCKETS, +}; + +// For testing +class MockProfileData; + +// Try to keep as small as possible, should match ASHMEM_SIZE in +// GraphicsStatsService.java +class ProfileData { + PREVENT_COPY_AND_ASSIGN(ProfileData); + +public: + ProfileData() { reset(); } + + void reset(); + void mergeWith(const ProfileData& other); + void dump(int fd) const; + uint32_t findPercentile(int percentile) const; + + void reportFrame(int64_t duration); + void reportJank() { mJankFrameCount++; } + void reportJankType(JankType type) { mJankTypeCounts[static_cast<int>(type)]++; } + + uint32_t totalFrameCount() const { return mTotalFrameCount; } + uint32_t jankFrameCount() const { return mJankFrameCount; } + nsecs_t statsStartTime() const { return mStatStartTime; } + uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; } + + struct HistogramEntry { + uint32_t renderTimeMs; + uint32_t frameCount; + }; + void histogramForEach(const std::function<void(HistogramEntry)>& callback) const; + + constexpr static int HistogramSize() { + return std::tuple_size<decltype(ProfileData::mFrameCounts)>::value + + std::tuple_size<decltype(ProfileData::mSlowFrameCounts)>::value; + } + + // Visible for testing + static uint32_t frameTimeForFrameCountIndex(uint32_t index); + static uint32_t frameTimeForSlowFrameCountIndex(uint32_t index); + +private: + // Open our guts up to unit tests + friend class MockProfileData; + + std::array <uint32_t, NUM_BUCKETS> mJankTypeCounts; + // See comments on kBucket* constants for what this holds + std::array<uint32_t, 57> mFrameCounts; + // Holds a histogram of frame times in 50ms increments from 150ms to 5s + std::array<uint16_t, 97> mSlowFrameCounts; + + uint32_t mTotalFrameCount; + uint32_t mJankFrameCount; + nsecs_t mStatStartTime; +}; + +// For testing +class MockProfileData : public ProfileData { +public: + std::array<uint32_t, NUM_BUCKETS>& editJankTypeCounts() { return mJankTypeCounts; } + std::array<uint32_t, 57>& editFrameCounts() { return mFrameCounts; } + std::array<uint16_t, 97>& editSlowFrameCounts() { return mSlowFrameCounts; } + uint32_t& editTotalFrameCount() { return mTotalFrameCount; } + uint32_t& editJankFrameCount() { return mJankFrameCount; } + nsecs_t& editStatStartTime() { return mStatStartTime; } +}; + +} /* namespace uirenderer */ +} /* namespace android */ + diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp index 87eaa6add459..3a77195824ad 100644 --- a/libs/hwui/service/GraphicsStatsService.cpp +++ b/libs/hwui/service/GraphicsStatsService.cpp @@ -38,9 +38,7 @@ constexpr int32_t sCurrentFileVersion = 1; constexpr int32_t sHeaderSize = 4; static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong"); -constexpr int sHistogramSize = - std::tuple_size<decltype(ProfileData::frameCounts)>::value + - std::tuple_size<decltype(ProfileData::slowFrameCounts)>::value; +constexpr int sHistogramSize = ProfileData::HistogramSize(); static void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::string& package, int versionCode, int64_t startTime, int64_t endTime, @@ -172,18 +170,18 @@ void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::st proto->set_package_name(package); proto->set_version_code(versionCode); auto summary = proto->mutable_summary(); - summary->set_total_frames(summary->total_frames() + data->totalFrameCount); - summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount); + summary->set_total_frames(summary->total_frames() + data->totalFrameCount()); + summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount()); summary->set_missed_vsync_count( - summary->missed_vsync_count() + data->jankTypeCounts[kMissedVsync]); + summary->missed_vsync_count() + data->jankTypeCount(kMissedVsync)); summary->set_high_input_latency_count( - summary->high_input_latency_count() + data->jankTypeCounts[kHighInputLatency]); + summary->high_input_latency_count() + data->jankTypeCount(kHighInputLatency)); summary->set_slow_ui_thread_count( - summary->slow_ui_thread_count() + data->jankTypeCounts[kSlowUI]); + summary->slow_ui_thread_count() + data->jankTypeCount(kSlowUI)); summary->set_slow_bitmap_upload_count( - summary->slow_bitmap_upload_count() + data->jankTypeCounts[kSlowSync]); + summary->slow_bitmap_upload_count() + data->jankTypeCount(kSlowSync)); summary->set_slow_draw_count( - summary->slow_draw_count() + data->jankTypeCounts[kSlowRT]); + summary->slow_draw_count() + data->jankTypeCount(kSlowRT)); bool creatingHistogram = false; if (proto->histogram_size() == 0) { @@ -193,33 +191,20 @@ void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::st LOG_ALWAYS_FATAL("Histogram size mismatch, proto is %d expected %d", proto->histogram_size(), sHistogramSize); } - for (size_t i = 0; i < data->frameCounts.size(); i++) { + int index = 0; + data->histogramForEach([&](ProfileData::HistogramEntry entry) { service::GraphicsStatsHistogramBucketProto* bucket; - int32_t renderTime = JankTracker::frameTimeForFrameCountIndex(i); if (creatingHistogram) { bucket = proto->add_histogram(); - bucket->set_render_millis(renderTime); + bucket->set_render_millis(entry.renderTimeMs); } else { - bucket = proto->mutable_histogram(i); - LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime, - "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime); + bucket = proto->mutable_histogram(index); + LOG_ALWAYS_FATAL_IF(bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs), + "Frame time mistmatch %d vs. %u", bucket->render_millis(), entry.renderTimeMs); } - bucket->set_frame_count(bucket->frame_count() + data->frameCounts[i]); - } - for (size_t i = 0; i < data->slowFrameCounts.size(); i++) { - service::GraphicsStatsHistogramBucketProto* bucket; - int32_t renderTime = JankTracker::frameTimeForSlowFrameCountIndex(i); - if (creatingHistogram) { - bucket = proto->add_histogram(); - bucket->set_render_millis(renderTime); - } else { - constexpr int offset = std::tuple_size<decltype(ProfileData::frameCounts)>::value; - bucket = proto->mutable_histogram(offset + i); - LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime, - "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime); - } - bucket->set_frame_count(bucket->frame_count() + data->slowFrameCounts[i]); - } + bucket->set_frame_count(bucket->frame_count() + entry.frameCount); + index++; + }); } static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile) { diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp index f6f73377e147..fda3a79a69da 100644 --- a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp +++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp @@ -61,17 +61,17 @@ TEST(GraphicsStats, findRootPath) { TEST(GraphicsStats, saveLoad) { std::string path = findRootPath() + "/test_saveLoad"; std::string packageName = "com.test.saveLoad"; - ProfileData mockData; - mockData.jankFrameCount = 20; - mockData.totalFrameCount = 100; - mockData.statStartTime = 10000; + MockProfileData mockData; + mockData.editJankFrameCount() = 20; + mockData.editTotalFrameCount() = 100; + mockData.editStatStartTime() = 10000; // Fill with patterned data we can recognize but which won't map to a // memset or basic for iteration count - for (size_t i = 0; i < mockData.frameCounts.size(); i++) { - mockData.frameCounts[i] = ((i % 10) + 1) * 2; + for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) { + mockData.editFrameCounts()[i] = ((i % 10) + 1) * 2; } - for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) { - mockData.slowFrameCounts[i] = (i % 5) + 1; + for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) { + mockData.editSlowFrameCounts()[i] = (i % 5) + 1; } GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData); service::GraphicsStatsProto loadedProto; @@ -87,17 +87,17 @@ TEST(GraphicsStats, saveLoad) { ASSERT_TRUE(loadedProto.has_summary()); EXPECT_EQ(20, loadedProto.summary().janky_frames()); EXPECT_EQ(100, loadedProto.summary().total_frames()); - EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(), + EXPECT_EQ(mockData.editFrameCounts().size() + mockData.editSlowFrameCounts().size(), (size_t) loadedProto.histogram_size()); for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) { int expectedCount, expectedBucket; - if (i < mockData.frameCounts.size()) { + if (i < mockData.editFrameCounts().size()) { expectedCount = ((i % 10) + 1) * 2; - expectedBucket = JankTracker::frameTimeForFrameCountIndex(i); + expectedBucket = ProfileData::frameTimeForFrameCountIndex(i); } else { - int temp = i - mockData.frameCounts.size(); + int temp = i - mockData.editFrameCounts().size(); expectedCount = (temp % 5) + 1; - expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp); + expectedBucket = ProfileData::frameTimeForSlowFrameCountIndex(temp); } EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count()); EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis()); @@ -107,26 +107,26 @@ TEST(GraphicsStats, saveLoad) { TEST(GraphicsStats, merge) { std::string path = findRootPath() + "/test_merge"; std::string packageName = "com.test.merge"; - ProfileData mockData; - mockData.jankFrameCount = 20; - mockData.totalFrameCount = 100; - mockData.statStartTime = 10000; + MockProfileData mockData; + mockData.editJankFrameCount() = 20; + mockData.editTotalFrameCount() = 100; + mockData.editStatStartTime() = 10000; // Fill with patterned data we can recognize but which won't map to a // memset or basic for iteration count - for (size_t i = 0; i < mockData.frameCounts.size(); i++) { - mockData.frameCounts[i] = ((i % 10) + 1) * 2; + for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) { + mockData.editFrameCounts()[i] = ((i % 10) + 1) * 2; } - for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) { - mockData.slowFrameCounts[i] = (i % 5) + 1; + for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) { + mockData.editSlowFrameCounts()[i] = (i % 5) + 1; } GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData); - mockData.jankFrameCount = 50; - mockData.totalFrameCount = 500; - for (size_t i = 0; i < mockData.frameCounts.size(); i++) { - mockData.frameCounts[i] = (i % 5) + 1; + mockData.editJankFrameCount() = 50; + mockData.editTotalFrameCount() = 500; + for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) { + mockData.editFrameCounts()[i] = (i % 5) + 1; } - for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) { - mockData.slowFrameCounts[i] = ((i % 10) + 1) * 2; + for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) { + mockData.editSlowFrameCounts()[i] = ((i % 10) + 1) * 2; } GraphicsStatsService::saveBuffer(path, packageName, 5, 7050, 10000, &mockData); @@ -143,19 +143,19 @@ TEST(GraphicsStats, merge) { ASSERT_TRUE(loadedProto.has_summary()); EXPECT_EQ(20 + 50, loadedProto.summary().janky_frames()); EXPECT_EQ(100 + 500, loadedProto.summary().total_frames()); - EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(), + EXPECT_EQ(mockData.editFrameCounts().size() + mockData.editSlowFrameCounts().size(), (size_t) loadedProto.histogram_size()); for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) { int expectedCount, expectedBucket; - if (i < mockData.frameCounts.size()) { + if (i < mockData.editFrameCounts().size()) { expectedCount = ((i % 10) + 1) * 2; expectedCount += (i % 5) + 1; - expectedBucket = JankTracker::frameTimeForFrameCountIndex(i); + expectedBucket = ProfileData::frameTimeForFrameCountIndex(i); } else { - int temp = i - mockData.frameCounts.size(); + int temp = i - mockData.editFrameCounts().size(); expectedCount = (temp % 5) + 1; expectedCount += ((temp % 10) + 1) * 2; - expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp); + expectedBucket = ProfileData::frameTimeForSlowFrameCountIndex(temp); } EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count()); EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis()); |