/* * Copyright 2019 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 #include #include #include #include #include #include #include #include #include "DisplayHardware/Hal.h" #include "TimeStats/TimeStats.h" namespace android::scheduler { /** * Class to encapsulate statistics about refresh rates that the display is using. When the power * mode is set to HWC_POWER_MODE_NORMAL, SF is switching between refresh rates that are stored in * the device's configs. Otherwise, we assume the HWC is running in power saving mode under the * hood (eg. the device is in DOZE, or screen off mode). */ class RefreshRateStats { static constexpr int64_t MS_PER_S = 1000; static constexpr int64_t MS_PER_MIN = 60 * MS_PER_S; static constexpr int64_t MS_PER_HOUR = 60 * MS_PER_MIN; static constexpr int64_t MS_PER_DAY = 24 * MS_PER_HOUR; using PowerMode = android::hardware::graphics::composer::hal::PowerMode; public: // TODO(b/185535769): Inject clock to avoid sleeping in tests. RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate) : mTimeStats(timeStats), mCurrentRefreshRate(currentRefreshRate), mCurrentPowerMode(PowerMode::OFF) {} void setPowerMode(PowerMode mode) { if (mCurrentPowerMode == mode) { return; } flushTime(); mCurrentPowerMode = mode; } // Sets config mode. If the mode has changed, it records how much time was spent in the previous // mode. void setRefreshRate(Fps currRefreshRate) { if (isApproxEqual(mCurrentRefreshRate, currRefreshRate)) { return; } mTimeStats.incrementRefreshRateSwitches(); flushTime(); mCurrentRefreshRate = currRefreshRate; } // Maps stringified refresh rate to total time spent in that mode. using TotalTimes = ftl::SmallMap; TotalTimes getTotalTimes() { // If the power mode is on, then we are probably switching between the config modes. If // it's not then the screen is probably off. Make sure to flush times before printing // them. flushTime(); TotalTimes totalTimes = ftl::init::map("ScreenOff", mScreenOffTime); // Sum the times for modes that map to the same name, e.g. "60 Hz". for (const auto& [fps, time] : mFpsTotalTimes) { const auto string = to_string(fps); const auto total = std::as_const(totalTimes) .get(string) .or_else(ftl::static_ref([] { using namespace std::chrono_literals; return 0ms; })) .value(); totalTimes.emplace_or_replace(string, total.get() + time); } return totalTimes; } // Traverses through the map of config modes and returns how long they've been running in easy // to read format. void dump(std::string& result) const { std::ostringstream stream("+ Refresh rate: running time in seconds\n"); for (const auto& [name, time] : const_cast(this)->getTotalTimes()) { stream << name << ": " << getDateFormatFromMs(time) << '\n'; } result.append(stream.str()); } private: // Calculates the time that passed in ms between the last time we recorded time and the time // this method was called. void flushTime() { const nsecs_t currentTime = systemTime(); const nsecs_t timeElapsed = currentTime - mPreviousRecordedTime; mPreviousRecordedTime = currentTime; const auto duration = std::chrono::milliseconds{ns2ms(timeElapsed)}; uint32_t fps = 0; if (mCurrentPowerMode == PowerMode::ON) { // Normal power mode is counted under different config modes. const auto total = std::as_const(mFpsTotalTimes) .get(mCurrentRefreshRate) .or_else(ftl::static_ref([] { using namespace std::chrono_literals; return 0ms; })) .value(); mFpsTotalTimes.emplace_or_replace(mCurrentRefreshRate, total.get() + duration); fps = static_cast(mCurrentRefreshRate.getIntValue()); } else { mScreenOffTime += duration; } mTimeStats.recordRefreshRate(fps, timeElapsed); } // Formats the time in milliseconds into easy to read format. static std::string getDateFormatFromMs(std::chrono::milliseconds time) { auto [days, dayRemainderMs] = std::div(static_cast(time.count()), MS_PER_DAY); auto [hours, hourRemainderMs] = std::div(dayRemainderMs, MS_PER_HOUR); auto [mins, minsRemainderMs] = std::div(hourRemainderMs, MS_PER_MIN); auto [sec, secRemainderMs] = std::div(minsRemainderMs, MS_PER_S); return base::StringPrintf("%" PRId64 "d%02" PRId64 ":%02" PRId64 ":%02" PRId64 ".%03" PRId64, days, hours, mins, sec, secRemainderMs); } // Aggregate refresh rate statistics for telemetry. TimeStats& mTimeStats; Fps mCurrentRefreshRate; PowerMode mCurrentPowerMode; ftl::SmallMap mFpsTotalTimes; std::chrono::milliseconds mScreenOffTime = std::chrono::milliseconds::zero(); nsecs_t mPreviousRecordedTime = systemTime(); }; } // namespace android::scheduler