1 /* 2 * Copyright 2019 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 #pragma once 18 19 #include <chrono> 20 #include <cinttypes> 21 #include <cstdlib> 22 #include <string> 23 24 #include <android-base/stringprintf.h> 25 #include <ftl/algorithm.h> 26 #include <ftl/small_map.h> 27 #include <utils/Timers.h> 28 29 #include <scheduler/Fps.h> 30 31 #include "DisplayHardware/Hal.h" 32 #include "TimeStats/TimeStats.h" 33 34 namespace android::scheduler { 35 36 /** 37 * Class to encapsulate statistics about refresh rates that the display is using. When the power 38 * mode is set to HWC_POWER_MODE_NORMAL, SF is switching between refresh rates that are stored in 39 * the device's configs. Otherwise, we assume the HWC is running in power saving mode under the 40 * hood (eg. the device is in DOZE, or screen off mode). 41 */ 42 class RefreshRateStats { 43 static constexpr int64_t MS_PER_S = 1000; 44 static constexpr int64_t MS_PER_MIN = 60 * MS_PER_S; 45 static constexpr int64_t MS_PER_HOUR = 60 * MS_PER_MIN; 46 static constexpr int64_t MS_PER_DAY = 24 * MS_PER_HOUR; 47 48 using PowerMode = android::hardware::graphics::composer::hal::PowerMode; 49 50 public: 51 // TODO(b/185535769): Inject clock to avoid sleeping in tests. RefreshRateStats(TimeStats & timeStats,Fps currentRefreshRate,PowerMode currentPowerMode)52 RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate, PowerMode currentPowerMode) 53 : mTimeStats(timeStats), 54 mCurrentRefreshRate(currentRefreshRate), 55 mCurrentPowerMode(currentPowerMode) {} 56 setPowerMode(PowerMode mode)57 void setPowerMode(PowerMode mode) { 58 if (mCurrentPowerMode == mode) { 59 return; 60 } 61 flushTime(); 62 mCurrentPowerMode = mode; 63 } 64 65 // Sets config mode. If the mode has changed, it records how much time was spent in the previous 66 // mode. setRefreshRate(Fps currRefreshRate)67 void setRefreshRate(Fps currRefreshRate) { 68 if (isApproxEqual(mCurrentRefreshRate, currRefreshRate)) { 69 return; 70 } 71 mTimeStats.incrementRefreshRateSwitches(); 72 flushTime(); 73 mCurrentRefreshRate = currRefreshRate; 74 } 75 76 // Maps stringified refresh rate to total time spent in that mode. 77 using TotalTimes = ftl::SmallMap<std::string, std::chrono::milliseconds, 3>; 78 getTotalTimes()79 TotalTimes getTotalTimes() { 80 // If the power mode is on, then we are probably switching between the config modes. If 81 // it's not then the screen is probably off. Make sure to flush times before printing 82 // them. 83 flushTime(); 84 85 TotalTimes totalTimes = ftl::init::map("ScreenOff", mScreenOffTime); 86 87 // Sum the times for modes that map to the same name, e.g. "60 Hz". 88 for (const auto& [fps, time] : mFpsTotalTimes) { 89 const auto string = to_string(fps); 90 const auto total = std::as_const(totalTimes) 91 .get(string) 92 .or_else(ftl::static_ref<std::chrono::milliseconds>([] { 93 using namespace std::chrono_literals; 94 return 0ms; 95 })) 96 .value(); 97 98 totalTimes.emplace_or_replace(string, total.get() + time); 99 } 100 101 return totalTimes; 102 } 103 104 // Traverses through the map of config modes and returns how long they've been running in easy 105 // to read format. dump(std::string & result)106 void dump(std::string& result) const { 107 std::ostringstream stream("+ Refresh rate: running time in seconds\n"); 108 109 for (const auto& [name, time] : const_cast<RefreshRateStats*>(this)->getTotalTimes()) { 110 stream << name << ": " << getDateFormatFromMs(time) << '\n'; 111 } 112 result.append(stream.str()); 113 } 114 115 private: 116 // Calculates the time that passed in ms between the last time we recorded time and the time 117 // this method was called. flushTime()118 void flushTime() { 119 const nsecs_t currentTime = systemTime(); 120 const nsecs_t timeElapsed = currentTime - mPreviousRecordedTime; 121 mPreviousRecordedTime = currentTime; 122 123 const auto duration = std::chrono::milliseconds{ns2ms(timeElapsed)}; 124 uint32_t fps = 0; 125 126 if (mCurrentPowerMode == PowerMode::ON) { 127 // Normal power mode is counted under different config modes. 128 const auto total = std::as_const(mFpsTotalTimes) 129 .get(mCurrentRefreshRate) 130 .or_else(ftl::static_ref<std::chrono::milliseconds>([] { 131 using namespace std::chrono_literals; 132 return 0ms; 133 })) 134 .value(); 135 136 mFpsTotalTimes.emplace_or_replace(mCurrentRefreshRate, total.get() + duration); 137 138 fps = static_cast<uint32_t>(mCurrentRefreshRate.getIntValue()); 139 } else { 140 mScreenOffTime += duration; 141 } 142 mTimeStats.recordRefreshRate(fps, timeElapsed); 143 } 144 145 // Formats the time in milliseconds into easy to read format. getDateFormatFromMs(std::chrono::milliseconds time)146 static std::string getDateFormatFromMs(std::chrono::milliseconds time) { 147 auto [days, dayRemainderMs] = std::div(static_cast<int64_t>(time.count()), MS_PER_DAY); 148 auto [hours, hourRemainderMs] = std::div(dayRemainderMs, MS_PER_HOUR); 149 auto [mins, minsRemainderMs] = std::div(hourRemainderMs, MS_PER_MIN); 150 auto [sec, secRemainderMs] = std::div(minsRemainderMs, MS_PER_S); 151 return base::StringPrintf("%" PRId64 "d%02" PRId64 ":%02" PRId64 ":%02" PRId64 152 ".%03" PRId64, 153 days, hours, mins, sec, secRemainderMs); 154 } 155 156 // Aggregate refresh rate statistics for telemetry. 157 TimeStats& mTimeStats; 158 159 Fps mCurrentRefreshRate; 160 PowerMode mCurrentPowerMode; 161 162 ftl::SmallMap<Fps, std::chrono::milliseconds, 2, FpsApproxEqual> mFpsTotalTimes; 163 std::chrono::milliseconds mScreenOffTime = std::chrono::milliseconds::zero(); 164 165 nsecs_t mPreviousRecordedTime = systemTime(); 166 }; 167 168 } // namespace android::scheduler 169