1 /*
2 * Copyright (C) 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 #include "VariableRefreshRateStatistic.h"
18
19 namespace android::hardware::graphics::composer {
20
VariableRefreshRateStatistic(CommonDisplayContextProvider * displayContextProvider,EventQueue * eventQueue,int maxFrameRate,int maxTeFrequency,int64_t updatePeriodNs)21 VariableRefreshRateStatistic::VariableRefreshRateStatistic(
22 CommonDisplayContextProvider* displayContextProvider, EventQueue* eventQueue,
23 int maxFrameRate, int maxTeFrequency, int64_t updatePeriodNs)
24 : mDisplayContextProvider(displayContextProvider),
25 mEventQueue(eventQueue),
26 mMaxFrameRate(maxFrameRate),
27 mMaxTeFrequency(maxTeFrequency),
28 mMinFrameIntervalNs(roundDivide(std::nano::den, static_cast<int64_t>(maxFrameRate))),
29 mTeFrequency(maxFrameRate),
30 mTeIntervalNs(roundDivide(std::nano::den, static_cast<int64_t>(mTeFrequency))),
31 mUpdatePeriodNs(updatePeriodNs) {
32 mStartStatisticTimeNs = getBootClockTimeNs();
33
34 // For debugging purposes, this will only be triggered when DEBUG_VRR_STATISTICS is defined.
35 #ifdef DEBUG_VRR_STATISTICS
36 auto configs = mDisplayContextProvider->getDisplayConfigs();
37 for (const auto& config : *configs) {
38 ALOGI("VariableRefreshRateStatistic: config id = %d : %s", config.first,
39 config.second.toString().c_str());
40 }
41 mUpdateEvent.mEventType = VrrControllerEventType::kStaticticUpdate;
42 mUpdateEvent.mFunctor =
43 std::move(std::bind(&VariableRefreshRateStatistic::updateStatistic, this));
44 mUpdateEvent.mWhenNs = getSteadyClockTimeNs() + mUpdatePeriodNs;
45 mEventQueue->mPriorityQueue.emplace(mUpdateEvent);
46 #endif
47 mStatistics[mDisplayRefreshProfile] = DisplayRefreshRecord();
48 }
49
getPowerOffDurationNs() const50 uint64_t VariableRefreshRateStatistic::getPowerOffDurationNs() const {
51 if (isPowerModeOffNowLocked()) {
52 const auto& item = mStatistics.find(mDisplayRefreshProfile);
53 if (item == mStatistics.end()) {
54 ALOGE("%s We should have inserted power-off item in constructor.", __func__);
55 return 0;
56 }
57 return mPowerOffDurationNs +
58 (getBootClockTimeNs() - item->second.mLastTimeStampInBootClockNs);
59 } else {
60 return mPowerOffDurationNs;
61 }
62 }
63
getStartStatisticTimeNs() const64 uint64_t VariableRefreshRateStatistic::getStartStatisticTimeNs() const {
65 return mStartStatisticTimeNs;
66 }
67
getStatistics()68 DisplayRefreshStatistics VariableRefreshRateStatistic::getStatistics() {
69 updateIdleStats();
70 std::scoped_lock lock(mMutex);
71 return mStatistics;
72 }
73
getUpdatedStatistics()74 DisplayRefreshStatistics VariableRefreshRateStatistic::getUpdatedStatistics() {
75 updateIdleStats();
76 std::scoped_lock lock(mMutex);
77 DisplayRefreshStatistics updatedStatistics;
78 for (auto& it : mStatistics) {
79 if (it.second.mUpdated) {
80 if (it.first.mNumVsync < 0) {
81 it.second.mAccumulatedTimeNs = getPowerOffDurationNs();
82 }
83 }
84 // need all mStatistics to be able to do aggregation and bucketing accurately
85 updatedStatistics[it.first] = it.second;
86 }
87 if (isPowerModeOffNowLocked()) {
88 mStatistics[mDisplayRefreshProfile].mUpdated = true;
89 }
90
91 return std::move(updatedStatistics);
92 }
93
dumpStatistics(bool getUpdatedOnly,RefreshSource refreshSource,const std::string & delimiter)94 std::string VariableRefreshRateStatistic::dumpStatistics(bool getUpdatedOnly,
95 RefreshSource refreshSource,
96 const std::string& delimiter) {
97 std::string res;
98 updateIdleStats();
99 std::scoped_lock lock(mMutex);
100 for (auto& it : mStatistics) {
101 if ((!getUpdatedOnly) || (it.second.mUpdated)) {
102 if (it.first.mRefreshSource & refreshSource) {
103 if (it.first.mNumVsync < 0) {
104 it.second.mAccumulatedTimeNs = getPowerOffDurationNs();
105 }
106 res += "[";
107 res += it.first.toString();
108 res += " , ";
109 res += it.second.toString();
110 res += "]";
111 res += delimiter;
112 }
113 }
114 }
115 return res;
116 }
117
normalizeString(const std::string & input)118 std::string VariableRefreshRateStatistic::normalizeString(const std::string& input) {
119 static constexpr int kDesiredLength = 30;
120 static constexpr int kSpaceWidth = 1;
121 int extraSpacesNeeded = std::max(0, (kDesiredLength - static_cast<int>(input.length())));
122 return input + std::string(extraSpacesNeeded, ' ');
123 }
124
dump(String8 & result,const std::vector<std::string> & args)125 void VariableRefreshRateStatistic::dump(String8& result, const std::vector<std::string>& args) {
126 bool hasDelta = false;
127
128 if (!args.empty()) {
129 for (const auto& arg : args) {
130 std::string lowercaseArg = arg;
131 std::transform(lowercaseArg.begin(), lowercaseArg.end(), lowercaseArg.begin(),
132 [](unsigned char c) { return std::tolower(c); });
133
134 if (lowercaseArg.find("delta") != std::string::npos) {
135 hasDelta = true;
136 }
137 }
138 }
139
140 auto updatedStatistics = getUpdatedStatistics();
141 auto curTime = getSteadyClockTimeNs();
142 std::map<std::string, DisplayRefreshRecord, StateNameComparator> aggregatedStats;
143 std::map<std::string, DisplayRefreshRecord> aggregatedStatsSnapshot;
144 // Aggregating lastSnapshot dumpsys to calculate delta
145 for (const auto& it : mStatisticsSnapshot) {
146 PowerStatsProfile profile = it.first.toPowerStatsProfile(false);
147 std::string stateName = mPowerStatsProfileTokenGenerator.generateStateName(&profile, false);
148 aggregatedStatsSnapshot[stateName] += it.second;
149 }
150
151 for (const auto& it : updatedStatistics) {
152 PowerStatsProfile profile = it.first.toPowerStatsProfile(false);
153 std::string stateName = mPowerStatsProfileTokenGenerator.generateStateName(&profile, false);
154 aggregatedStats[stateName] += it.second;
155 }
156
157 if (hasDelta) {
158 result.appendFormat("Elapsed Time: %" PRId64 " \n", (curTime - mLastDumpsysTime) / 1000000);
159 }
160
161 std::string headerString = hasDelta ? normalizeString("StateName") + "\t" +
162 normalizeString("Total Time (ms)") + "\t" + normalizeString("Delta") + "\t" +
163 normalizeString("Total Entries") + "\t" + normalizeString("Delta") + "\t" +
164 normalizeString("Last Entry TStamp (ms)") + "\t" + normalizeString("Delta")
165 : normalizeString("StateName") + "\t" +
166 normalizeString("Total Time (ms)") + "\t" + normalizeString("Total Entries") +
167 "\t" + normalizeString("Last Entry TStamp (ms)");
168
169 result.appendFormat("%s \n", headerString.c_str());
170
171 for (const auto& it : aggregatedStats) {
172 uint64_t countDelta = 0;
173 uint64_t accumulatedTimeNsDelta = 0;
174 uint64_t lastTimeStampInBootClockNsDelta = 0;
175
176 auto agIt = aggregatedStatsSnapshot.find(it.first);
177 if (agIt != aggregatedStatsSnapshot.end()) {
178 countDelta = it.second.mCount - agIt->second.mCount;
179 accumulatedTimeNsDelta = it.second.mAccumulatedTimeNs - agIt->second.mAccumulatedTimeNs;
180 lastTimeStampInBootClockNsDelta = it.second.mLastTimeStampInBootClockNs -
181 agIt->second.mLastTimeStampInBootClockNs;
182 }
183
184 std::string statsString = hasDelta
185 ? normalizeString(it.first) + "\t" +
186 normalizeString(std::to_string(it.second.mAccumulatedTimeNs / 1000000)) +
187 "\t" + normalizeString(std::to_string(accumulatedTimeNsDelta / 1000000)) +
188 "\t" + normalizeString(std::to_string(it.second.mCount)) + "\t" +
189 normalizeString(std::to_string(countDelta)) + "\t" +
190 normalizeString(
191 std::to_string(it.second.mLastTimeStampInBootClockNs / 1000000)) +
192 "\t" +
193 normalizeString(std::to_string(lastTimeStampInBootClockNsDelta / 1000000))
194 :
195
196 normalizeString(it.first) + "\t" +
197 normalizeString(std::to_string(it.second.mAccumulatedTimeNs / 1000000)) +
198 "\t" + normalizeString(std::to_string(it.second.mCount)) + "\t" +
199 normalizeString(
200 std::to_string(it.second.mLastTimeStampInBootClockNs / 1000000));
201
202 result.appendFormat("%s \n", statsString.c_str());
203 }
204
205 // Take a snapshot of updatedStatistics and time
206 mLastDumpsysTime = curTime;
207 mStatisticsSnapshot = DisplayRefreshStatistics(updatedStatistics);
208 }
209
onPowerStateChange(int from,int to)210 void VariableRefreshRateStatistic::onPowerStateChange(int from, int to) {
211 if (from == to) {
212 return;
213 }
214 if (mDisplayRefreshProfile.mCurrentDisplayConfig.mPowerMode != from) {
215 ALOGE("%s Power mode mismatch between storing state(%d) and actual mode(%d)", __func__,
216 mDisplayRefreshProfile.mCurrentDisplayConfig.mPowerMode, from);
217 }
218 updateIdleStats();
219 std::scoped_lock lock(mMutex);
220 if (isPowerModeOff(to)) {
221 // Currently the for power stats both |HWC_POWER_MODE_OFF| and |HWC_POWER_MODE_DOZE_SUSPEND|
222 // are classified as "off" states in power statistics. Consequently,we assign the value of
223 // |HWC_POWER_MODE_OFF| to |mPowerMode| when it is |HWC_POWER_MODE_DOZE_SUSPEND|.
224 mDisplayRefreshProfile.mCurrentDisplayConfig.mPowerMode = HWC_POWER_MODE_OFF;
225
226 auto& record = mStatistics[mDisplayRefreshProfile];
227 ++record.mCount;
228 record.mLastTimeStampInBootClockNs = getBootClockTimeNs();
229 record.mUpdated = true;
230
231 mLastRefreshTimeInBootClockNs = kDefaultInvalidPresentTimeNs;
232 } else {
233 if (isPowerModeOff(from)) {
234 mPowerOffDurationNs +=
235 (getBootClockTimeNs() -
236 mStatistics[mDisplayRefreshProfile].mLastTimeStampInBootClockNs);
237 }
238 mDisplayRefreshProfile.mCurrentDisplayConfig.mPowerMode = to;
239 if (to == HWC_POWER_MODE_DOZE) {
240 mDisplayRefreshProfile.mNumVsync = mTeFrequency;
241 auto& record = mStatistics[mDisplayRefreshProfile];
242 ++record.mCount;
243 record.mLastTimeStampInBootClockNs = getBootClockTimeNs();
244 record.mUpdated = true;
245 }
246 }
247 }
248
onPresent(int64_t presentTimeNs,int flag)249 void VariableRefreshRateStatistic::onPresent(int64_t presentTimeNs, int flag) {
250 onRefreshInternal(presentTimeNs, flag, RefreshSource::kRefreshSourceActivePresent);
251 }
252
onNonPresentRefresh(int64_t refreshTimeNs,RefreshSource refreshSource)253 void VariableRefreshRateStatistic::onNonPresentRefresh(int64_t refreshTimeNs,
254 RefreshSource refreshSource) {
255 onRefreshInternal(refreshTimeNs, 0, refreshSource);
256 }
257
onRefreshInternal(int64_t refreshTimeNs,int flag,RefreshSource refreshSource)258 void VariableRefreshRateStatistic::onRefreshInternal(int64_t refreshTimeNs, int flag,
259 RefreshSource refreshSource) {
260 int64_t presentTimeInBootClockNs = steadyClockTimeToBootClockTimeNs(refreshTimeNs);
261 if (mLastRefreshTimeInBootClockNs == kDefaultInvalidPresentTimeNs) {
262 mLastRefreshTimeInBootClockNs = presentTimeInBootClockNs;
263 updateCurrentDisplayStatus();
264 // Ignore first refresh after resume
265 return;
266 }
267 updateIdleStats(presentTimeInBootClockNs);
268 updateCurrentDisplayStatus();
269 if (hasPresentFrameFlag(flag, PresentFrameFlag::kPresentingWhenDoze)) {
270 // In low power mode, panel boost to 30 Hz while presenting new frame.
271 mDisplayRefreshProfile.mNumVsync = mTeFrequency / kFrameRateWhenPresentAtLpMode;
272 mLastRefreshTimeInBootClockNs =
273 presentTimeInBootClockNs + (std::nano::den / kFrameRateWhenPresentAtLpMode);
274 } else {
275 int numVsync = roundDivide((presentTimeInBootClockNs - mLastRefreshTimeInBootClockNs),
276 mTeIntervalNs);
277 // TODO(b/353976456): Implement a scheduler to avoid conflicts between present and
278 // non-present refresh. Currently, If a conflict occurs, both present and non-present
279 // refresh may request to take effect simultaneously, resulting in a zero duration between
280 // them. To address this, we avoid including statistics with zero duration. This issue
281 // should be resolved once the scheduler is implemented.
282 if (numVsync == 0) return;
283 numVsync = std::max(1, std::min(mTeFrequency, numVsync));
284 mDisplayRefreshProfile.mNumVsync = numVsync;
285 mLastRefreshTimeInBootClockNs = presentTimeInBootClockNs;
286 mDisplayRefreshProfile.mRefreshSource = refreshSource;
287 }
288 {
289 std::scoped_lock lock(mMutex);
290
291 auto& record = mStatistics[mDisplayRefreshProfile];
292 ++record.mCount;
293 record.mAccumulatedTimeNs += (mTeIntervalNs * mDisplayRefreshProfile.mNumVsync);
294 record.mLastTimeStampInBootClockNs = presentTimeInBootClockNs;
295 record.mUpdated = true;
296 if (hasPresentFrameFlag(flag, PresentFrameFlag::kPresentingWhenDoze)) {
297 // After presenting a frame in AOD, we revert back to 1 Hz operation.
298 mDisplayRefreshProfile.mNumVsync = mTeFrequency;
299 auto& record = mStatistics[mDisplayRefreshProfile];
300 ++record.mCount;
301 record.mLastTimeStampInBootClockNs = mLastRefreshTimeInBootClockNs;
302 record.mUpdated = true;
303 }
304 }
305 }
306
setActiveVrrConfiguration(int activeConfigId,int teFrequency)307 void VariableRefreshRateStatistic::setActiveVrrConfiguration(int activeConfigId, int teFrequency) {
308 updateIdleStats();
309 mDisplayRefreshProfile.mCurrentDisplayConfig.mActiveConfigId = activeConfigId;
310 mDisplayRefreshProfile.mWidth = mDisplayContextProvider->getWidth(activeConfigId);
311 mDisplayRefreshProfile.mHeight = mDisplayContextProvider->getHeight(activeConfigId);
312 mDisplayRefreshProfile.mTeFrequency = mDisplayContextProvider->getTeFrequency(activeConfigId);
313 mTeFrequency = teFrequency;
314 if (mTeFrequency % mMaxFrameRate != 0) {
315 ALOGW("%s TE frequency does not align with the maximum frame rate as a multiplier.",
316 __func__);
317 }
318 mTeIntervalNs = roundDivide(std::nano::den, static_cast<int64_t>(mTeFrequency));
319 // TODO(b/333204544): how can we handle the case if mTeFrequency % mMinimumRefreshRate != 0?
320 if ((mMinimumRefreshRate > 0) && (mTeFrequency % mMinimumRefreshRate != 0)) {
321 ALOGW("%s TE frequency does not align with the lowest frame rate as a multiplier.",
322 __func__);
323 }
324 }
325
setFixedRefreshRate(uint32_t rate)326 void VariableRefreshRateStatistic::setFixedRefreshRate(uint32_t rate) {
327 if (mMinimumRefreshRate != rate) {
328 updateIdleStats();
329 mMinimumRefreshRate = rate;
330 if (mMinimumRefreshRate > 1) {
331 mMaximumFrameIntervalNs =
332 roundDivide(std::nano::den, static_cast<int64_t>(mMinimumRefreshRate));
333 // TODO(b/333204544): how can we handle the case if mTeFrequency % mMinimumRefreshRate
334 // != 0?
335 if (mTeFrequency % mMinimumRefreshRate != 0) {
336 ALOGW("%s TE frequency does not align with the lowest frame rate as a multiplier.",
337 __func__);
338 }
339 } else {
340 mMaximumFrameIntervalNs = kMaxRefreshIntervalNs;
341 }
342 }
343 }
344
isPowerModeOffNowLocked() const345 bool VariableRefreshRateStatistic::isPowerModeOffNowLocked() const {
346 return isPowerModeOff(mDisplayRefreshProfile.mCurrentDisplayConfig.mPowerMode);
347 }
348
updateCurrentDisplayStatus()349 void VariableRefreshRateStatistic::updateCurrentDisplayStatus() {
350 mDisplayRefreshProfile.mCurrentDisplayConfig.mBrightnessMode =
351 mDisplayContextProvider->getBrightnessMode();
352 if (mDisplayRefreshProfile.mCurrentDisplayConfig.mBrightnessMode ==
353 BrightnessMode::kInvalidBrightnessMode) {
354 mDisplayRefreshProfile.mCurrentDisplayConfig.mBrightnessMode =
355 BrightnessMode::kNormalBrightnessMode;
356 }
357 }
358
updateIdleStats(int64_t endTimeStampInBootClockNs)359 void VariableRefreshRateStatistic::updateIdleStats(int64_t endTimeStampInBootClockNs) {
360 if (mDisplayRefreshProfile.isOff()) return;
361 if (mLastRefreshTimeInBootClockNs == kDefaultInvalidPresentTimeNs) return;
362
363 endTimeStampInBootClockNs =
364 endTimeStampInBootClockNs < 0 ? getBootClockTimeNs() : endTimeStampInBootClockNs;
365 auto durationFromLastPresentNs = endTimeStampInBootClockNs - mLastRefreshTimeInBootClockNs;
366 durationFromLastPresentNs = durationFromLastPresentNs < 0 ? 0 : durationFromLastPresentNs;
367 if (mDisplayRefreshProfile.mCurrentDisplayConfig.mPowerMode == HWC_POWER_MODE_DOZE) {
368 mDisplayRefreshProfile.mNumVsync = mTeFrequency;
369
370 std::scoped_lock lock(mMutex);
371
372 auto& record = mStatistics[mDisplayRefreshProfile];
373 record.mAccumulatedTimeNs += durationFromLastPresentNs;
374 record.mLastTimeStampInBootClockNs = mLastRefreshTimeInBootClockNs;
375 mLastRefreshTimeInBootClockNs = endTimeStampInBootClockNs;
376 record.mUpdated = true;
377 } else {
378 if ((mMinimumRefreshRate > 1) &&
379 (!isPresentRefresh(mDisplayRefreshProfile.mRefreshSource))) {
380 ALOGE("%s We should not have non-present refresh when the minimum refresh rate is set, "
381 "as it should use auto mode.",
382 __func__);
383 return;
384 }
385 mDisplayRefreshProfile.mRefreshSource = RefreshSource::kRefreshSourceIdlePresent;
386
387 int numVsync = roundDivide(durationFromLastPresentNs, mTeIntervalNs);
388 mDisplayRefreshProfile.mNumVsync =
389 (mMinimumRefreshRate > 1 ? (mTeFrequency / mMinimumRefreshRate) : mTeFrequency);
390 if (numVsync <= mDisplayRefreshProfile.mNumVsync) return;
391
392 // Ensure that the last vsync should not be included now, since it would be processed for
393 // next update or |onPresent|
394 auto count = (numVsync - 1) / mDisplayRefreshProfile.mNumVsync;
395 auto alignedDurationNs = mMaximumFrameIntervalNs * count;
396 {
397 std::scoped_lock lock(mMutex);
398
399 auto& record = mStatistics[mDisplayRefreshProfile];
400 record.mCount += count;
401 record.mAccumulatedTimeNs += alignedDurationNs;
402 mLastRefreshTimeInBootClockNs += alignedDurationNs;
403 record.mLastTimeStampInBootClockNs = mLastRefreshTimeInBootClockNs;
404 record.mUpdated = true;
405 }
406 }
407 }
408
409 #ifdef DEBUG_VRR_STATISTICS
updateStatistic()410 int VariableRefreshRateStatistic::updateStatistic() {
411 updateIdleStats();
412 for (const auto& it : mStatistics) {
413 const auto& key = it.first;
414 const auto& value = it.second;
415 ALOGD("%s: power mode = %d, id = %d, birghtness mode = %d, vsync "
416 "= %d : count = %ld, last entry time = %ld",
417 __func__, key.mCurrentDisplayConfig.mPowerMode,
418 key.mCurrentDisplayConfig.mActiveConfigId, key.mCurrentDisplayConfig.mBrightnessMode,
419 key.mNumVsync, value.mCount, value.mLastTimeStampInBootClockNs);
420 }
421 // Post next update statistics event.
422 mUpdateEvent.mWhenNs = getSteadyClockTimeNs() + mUpdatePeriodNs;
423 mEventQueue->mPriorityQueue.emplace(mUpdateEvent);
424
425 return NO_ERROR;
426 }
427 #endif
428
429 } // namespace android::hardware::graphics::composer
430