summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author John Reck <jreck@google.com> 2017-07-05 14:03:43 -0700
committer John Reck <jreck@google.com> 2017-07-05 14:04:51 -0700
commit7075c79209256101aee60584ee7e1d6f7f959c61 (patch)
tree4592cb397ed61aa5e98e5ab01cbe646418bd93c8
parent6f76e7f96d34215dcff29982e65d2f642e6578aa (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.bp1
-rw-r--r--libs/hwui/JankTracker.cpp152
-rw-r--r--libs/hwui/JankTracker.h31
-rw-r--r--libs/hwui/ProfileData.cpp177
-rw-r--r--libs/hwui/ProfileData.h109
-rw-r--r--libs/hwui/service/GraphicsStatsService.cpp49
-rw-r--r--libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp64
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());