1 /*
2 * Copyright 2024 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 #define LOG_TAG "powerhal-libperfmgr"
18
19 #include "SessionRecords.h"
20
21 #include <android-base/logging.h>
22
23 namespace aidl {
24 namespace google {
25 namespace hardware {
26 namespace power {
27 namespace impl {
28 namespace pixel {
29
30 static constexpr int32_t kTotalFramesForFPSCheck = 3;
31
SessionRecords(const int32_t maxNumOfRecords,const double jankCheckTimeFactor)32 SessionRecords::SessionRecords(const int32_t maxNumOfRecords, const double jankCheckTimeFactor)
33 : kMaxNumOfRecords(maxNumOfRecords), kJankCheckTimeFactor(jankCheckTimeFactor) {
34 mRecords.resize(maxNumOfRecords);
35 }
36
addReportedDurations(const std::vector<WorkDuration> & actualDurationsNs,int64_t targetDurationNs,FrameBuckets & newFramesInBuckets,bool computeFPSJitters)37 void SessionRecords::addReportedDurations(const std::vector<WorkDuration> &actualDurationsNs,
38 int64_t targetDurationNs,
39 FrameBuckets &newFramesInBuckets,
40 bool computeFPSJitters) {
41 for (auto &duration : actualDurationsNs) {
42 int32_t totalDurationUs = duration.durationNanos / 1000;
43
44 if (mNumOfFrames >= kMaxNumOfRecords) {
45 // Remove the oldest record when the number of records is greater
46 // than allowed.
47 int32_t indexOfRecordToRemove = (mLatestRecordIndex + 1) % kMaxNumOfRecords;
48 mSumOfDurationsUs -= mRecords[indexOfRecordToRemove].totalDurationUs;
49 if (mRecords[indexOfRecordToRemove].isMissedCycle) {
50 mNumOfMissedCycles--;
51 if (mNumOfMissedCycles < 0) {
52 LOG(ERROR) << "Invalid number of missed cycles: " << mNumOfMissedCycles;
53 }
54 }
55 if (mRecords[indexOfRecordToRemove].isFPSJitter) {
56 mNumOfFrameFPSJitters--;
57 if (mNumOfFrameFPSJitters < 0) {
58 LOG(ERROR) << "Invalid number of FPS jitter frames: " << mNumOfFrameFPSJitters;
59 }
60 }
61 mNumOfFrames--;
62
63 // If the record to be removed is the max duration, pop it out of the
64 // descending dequeue of record indexes.
65 if (mRecordsIndQueue.front() == indexOfRecordToRemove) {
66 mRecordsIndQueue.pop_front();
67 }
68 }
69
70 mLatestRecordIndex = (mLatestRecordIndex + 1) % kMaxNumOfRecords;
71
72 // Track start delay
73 auto startTimeNs = duration.timeStampNanos - duration.durationNanos;
74 int32_t startIntervalUs = 0;
75 if (mNumOfFrames > 0) {
76 startIntervalUs = (startTimeNs - mLastStartTimeNs) / 1000;
77 }
78 mLastStartTimeNs = startTimeNs;
79
80 // Track the number of frame FPS jitters.
81 // A frame is evaluated as FPS jitter if its startInterval is not less
82 // than previous three frames' average startIntervals.
83 bool FPSJitter = false;
84 if (computeFPSJitters) {
85 if (mAddedFramesForFPSCheck < kTotalFramesForFPSCheck) {
86 if (startIntervalUs > 0) {
87 mLatestStartIntervalSumUs += startIntervalUs;
88 mAddedFramesForFPSCheck++;
89 }
90 } else {
91 if (startIntervalUs > (1.4 * mLatestStartIntervalSumUs / kTotalFramesForFPSCheck)) {
92 FPSJitter = true;
93 mNumOfFrameFPSJitters++;
94 }
95 int32_t oldRecordIndex = mLatestRecordIndex - kTotalFramesForFPSCheck;
96 if (oldRecordIndex < 0) {
97 oldRecordIndex += kMaxNumOfRecords;
98 }
99 mLatestStartIntervalSumUs +=
100 startIntervalUs - mRecords[oldRecordIndex].startIntervalUs;
101 }
102 } else {
103 mLatestStartIntervalSumUs = 0;
104 mAddedFramesForFPSCheck = 0;
105 }
106
107 bool cycleMissed = totalDurationUs > (targetDurationNs / 1000) * kJankCheckTimeFactor;
108 mRecords[mLatestRecordIndex] =
109 CycleRecord{startIntervalUs, totalDurationUs, cycleMissed, FPSJitter};
110 mNumOfFrames++;
111 if (cycleMissed) {
112 mNumOfMissedCycles++;
113 }
114 updateFrameBuckets(totalDurationUs, cycleMissed, newFramesInBuckets);
115
116 // Pop out the indexes that their related values are not greater than the
117 // latest one.
118 while (!mRecordsIndQueue.empty() &&
119 (mRecords[mRecordsIndQueue.back()].totalDurationUs <= totalDurationUs)) {
120 mRecordsIndQueue.pop_back();
121 }
122 mRecordsIndQueue.push_back(mLatestRecordIndex);
123
124 mSumOfDurationsUs += totalDurationUs;
125 mAvgDurationUs = mSumOfDurationsUs / mNumOfFrames;
126 }
127 }
128
getMaxDuration()129 std::optional<int32_t> SessionRecords::getMaxDuration() {
130 if (mRecordsIndQueue.empty()) {
131 return std::nullopt;
132 }
133 return mRecords[mRecordsIndQueue.front()].totalDurationUs;
134 }
135
getAvgDuration()136 std::optional<int32_t> SessionRecords::getAvgDuration() {
137 if (mNumOfFrames <= 0) {
138 return std::nullopt;
139 }
140 return mAvgDurationUs;
141 }
142
getNumOfRecords()143 int32_t SessionRecords::getNumOfRecords() {
144 return mNumOfFrames;
145 }
146
getNumOfMissedCycles()147 int32_t SessionRecords::getNumOfMissedCycles() {
148 return mNumOfMissedCycles;
149 }
150
isLowFrameRate(int32_t fpsLowRateThreshold)151 bool SessionRecords::isLowFrameRate(int32_t fpsLowRateThreshold) {
152 // Check the last three records. If all of their start delays are larger
153 // than the cycle duration threshold, return "true".
154 auto cycleDurationThresholdUs = 1000000.0 / fpsLowRateThreshold;
155 if (mNumOfFrames >= 3) { // Todo: make this number as a tunable config
156 int32_t ind1 = mLatestRecordIndex;
157 int32_t ind2 = ind1 == 0 ? (kMaxNumOfRecords - 1) : (ind1 - 1);
158 int32_t ind3 = ind2 == 0 ? (kMaxNumOfRecords - 1) : (ind2 - 1);
159 return (mRecords[ind1].startIntervalUs >= cycleDurationThresholdUs) &&
160 (mRecords[ind2].startIntervalUs >= cycleDurationThresholdUs) &&
161 (mRecords[ind3].startIntervalUs >= cycleDurationThresholdUs);
162 }
163
164 return false;
165 }
166
resetRecords()167 void SessionRecords::resetRecords() {
168 mAvgDurationUs = 0;
169 mLastStartTimeNs = 0;
170 mLatestRecordIndex = -1;
171 mNumOfMissedCycles = 0;
172 mNumOfFrames = 0;
173 mSumOfDurationsUs = 0;
174 mRecordsIndQueue.clear();
175 }
176
getLatestFPS() const177 int32_t SessionRecords::getLatestFPS() const {
178 return 1000000 * kTotalFramesForFPSCheck / mLatestStartIntervalSumUs;
179 }
180
getNumOfFPSJitters() const181 int32_t SessionRecords::getNumOfFPSJitters() const {
182 return mNumOfFrameFPSJitters;
183 }
184
updateFrameBuckets(int32_t frameDurationUs,bool isJankFrame,FrameBuckets & framesInBuckets)185 void SessionRecords::updateFrameBuckets(int32_t frameDurationUs, bool isJankFrame,
186 FrameBuckets &framesInBuckets) {
187 framesInBuckets.totalNumOfFrames++;
188 if (!isJankFrame || frameDurationUs < 17000) {
189 return;
190 }
191
192 if (frameDurationUs < 25000) {
193 framesInBuckets.numOfFrames17to25ms++;
194 } else if (frameDurationUs < 34000) {
195 framesInBuckets.numOfFrames25to34ms++;
196 } else if (frameDurationUs < 67000) {
197 framesInBuckets.numOfFrames34to67ms++;
198 } else if (frameDurationUs < 100000) {
199 framesInBuckets.numOfFrames67to100ms++;
200 } else if (frameDurationUs >= 100000) {
201 framesInBuckets.numOfFramesOver100ms++;
202 }
203 }
204
205 } // namespace pixel
206 } // namespace impl
207 } // namespace power
208 } // namespace hardware
209 } // namespace google
210 } // namespace aidl
211