Plumb refresh and render rates into shared timeline

* Make the overridden frame rate from the Scheduler public so that
SurfaceFlinger can access it for each uid.
* Add the display refresh rate on each display frame
* Add the application rendering rate on each SurfaceFrame created
* If there is no application rendering rate, then set it to the display
refresh rate.
* Plumb all those metrics into TimeStats.
* Change global metrics to increment for every SurfaceFrame instead of
every DisplayFrame, so that the rendering rate dimension can be
accurately captured.

Bug: 172937287
Test: builds, boots, timestats dump
Change-Id: Icfd4cecfdfa5d6c434661cab91c624eb08e8baea
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 5d387d6..c3f3671 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -105,53 +105,63 @@
 AStatsManager_PullAtomCallbackReturn TimeStats::populateGlobalAtom(AStatsEventList* data) {
     std::lock_guard<std::mutex> lock(mMutex);
 
-    if (mTimeStats.statsStart == 0) {
+    if (mTimeStats.statsStartLegacy == 0) {
         return AStatsManager_PULL_SKIP;
     }
     flushPowerTimeLocked();
 
-    AStatsEvent* event = mStatsDelegate->addStatsEventToPullData(data);
-    mStatsDelegate->statsEventSetAtomId(event, android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
-    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.totalFrames);
-    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.missedFrames);
-    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.clientCompositionFrames);
-    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.displayOnTime);
-    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.presentToPresent.totalTime());
-    mStatsDelegate->statsEventWriteInt32(event, mTimeStats.displayEventConnectionsCount);
-    std::string frameDurationBytes =
-            histogramToProtoByteString(mTimeStats.frameDuration.hist, mMaxPulledHistogramBuckets);
-    mStatsDelegate->statsEventWriteByteArray(event, (const uint8_t*)frameDurationBytes.c_str(),
-                                             frameDurationBytes.size());
-    std::string renderEngineTimingBytes =
-            histogramToProtoByteString(mTimeStats.renderEngineTiming.hist,
-                                       mMaxPulledHistogramBuckets);
-    mStatsDelegate->statsEventWriteByteArray(event, (const uint8_t*)renderEngineTimingBytes.c_str(),
-                                             renderEngineTimingBytes.size());
+    for (const auto& globalSlice : mTimeStats.stats) {
+        AStatsEvent* event = mStatsDelegate->addStatsEventToPullData(data);
+        mStatsDelegate->statsEventSetAtomId(event, android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
+        mStatsDelegate->statsEventWriteInt64(event, mTimeStats.totalFramesLegacy);
+        mStatsDelegate->statsEventWriteInt64(event, mTimeStats.missedFramesLegacy);
+        mStatsDelegate->statsEventWriteInt64(event, mTimeStats.clientCompositionFramesLegacy);
+        mStatsDelegate->statsEventWriteInt64(event, mTimeStats.displayOnTimeLegacy);
+        mStatsDelegate->statsEventWriteInt64(event, mTimeStats.presentToPresentLegacy.totalTime());
+        mStatsDelegate->statsEventWriteInt32(event, mTimeStats.displayEventConnectionsCountLegacy);
+        std::string frameDurationBytes =
+                histogramToProtoByteString(mTimeStats.frameDurationLegacy.hist,
+                                           mMaxPulledHistogramBuckets);
+        mStatsDelegate->statsEventWriteByteArray(event, (const uint8_t*)frameDurationBytes.c_str(),
+                                                 frameDurationBytes.size());
+        std::string renderEngineTimingBytes =
+                histogramToProtoByteString(mTimeStats.renderEngineTimingLegacy.hist,
+                                           mMaxPulledHistogramBuckets);
+        mStatsDelegate->statsEventWriteByteArray(event,
+                                                 (const uint8_t*)renderEngineTimingBytes.c_str(),
+                                                 renderEngineTimingBytes.size());
 
-    mStatsDelegate->statsEventWriteInt32(event, mTimeStats.jankPayload.totalFrames);
-    mStatsDelegate->statsEventWriteInt32(event, mTimeStats.jankPayload.totalJankyFrames);
-    mStatsDelegate->statsEventWriteInt32(event, mTimeStats.jankPayload.totalSFLongCpu);
-    mStatsDelegate->statsEventWriteInt32(event, mTimeStats.jankPayload.totalSFLongGpu);
-    mStatsDelegate->statsEventWriteInt32(event, mTimeStats.jankPayload.totalSFUnattributed);
-    mStatsDelegate->statsEventWriteInt32(event, mTimeStats.jankPayload.totalAppUnattributed);
+        mStatsDelegate->statsEventWriteInt32(event, globalSlice.second.jankPayload.totalFrames);
+        mStatsDelegate->statsEventWriteInt32(event,
+                                             globalSlice.second.jankPayload.totalJankyFrames);
+        mStatsDelegate->statsEventWriteInt32(event, globalSlice.second.jankPayload.totalSFLongCpu);
+        mStatsDelegate->statsEventWriteInt32(event, globalSlice.second.jankPayload.totalSFLongGpu);
+        mStatsDelegate->statsEventWriteInt32(event,
+                                             globalSlice.second.jankPayload.totalSFUnattributed);
+        mStatsDelegate->statsEventWriteInt32(event,
+                                             globalSlice.second.jankPayload.totalAppUnattributed);
 
-    // TODO: populate these with real values
-    mStatsDelegate->statsEventWriteInt32(event, 0); // total_janky_frames_sf_scheduling
-    mStatsDelegate->statsEventWriteInt32(event, 0); // total_jank_frames_sf_prediction_error
-    mStatsDelegate->statsEventWriteInt32(event, 0); // total_jank_frames_app_buffer_stuffing
-    mStatsDelegate->statsEventWriteInt32(event, 0); // display_refresh_rate_bucket
-    std::string sfDeadlineMissedBytes =
-            histogramToProtoByteString(std::unordered_map<int32_t, int32_t>(),
-                                       mMaxPulledHistogramBuckets);
-    mStatsDelegate->statsEventWriteByteArray(event, (const uint8_t*)sfDeadlineMissedBytes.c_str(),
-                                             sfDeadlineMissedBytes.size()); // sf_deadline_misses
-    std::string sfPredictionErrorBytes =
-            histogramToProtoByteString(std::unordered_map<int32_t, int32_t>(),
-                                       mMaxPulledHistogramBuckets);
-    mStatsDelegate->statsEventWriteByteArray(event, (const uint8_t*)sfPredictionErrorBytes.c_str(),
-                                             sfPredictionErrorBytes.size()); // sf_prediction_errors
-    mStatsDelegate->statsEventWriteInt32(event, 0); // render_rate_bucket
-    mStatsDelegate->statsEventBuild(event);
+        // TODO: populate these with real values
+        mStatsDelegate->statsEventWriteInt32(event, 0); // total_janky_frames_sf_scheduling
+        mStatsDelegate->statsEventWriteInt32(event, 0); // total_jank_frames_sf_prediction_error
+        mStatsDelegate->statsEventWriteInt32(event, 0); // total_jank_frames_app_buffer_stuffing
+        mStatsDelegate->statsEventWriteInt32(event, globalSlice.first.displayRefreshRateBucket);
+        std::string sfDeadlineMissedBytes =
+                histogramToProtoByteString(std::unordered_map<int32_t, int32_t>(),
+                                           mMaxPulledHistogramBuckets);
+        mStatsDelegate
+                ->statsEventWriteByteArray(event, (const uint8_t*)sfDeadlineMissedBytes.c_str(),
+                                           sfDeadlineMissedBytes.size()); // sf_deadline_misses
+        std::string sfPredictionErrorBytes =
+                histogramToProtoByteString(std::unordered_map<int32_t, int32_t>(),
+                                           mMaxPulledHistogramBuckets);
+        mStatsDelegate
+                ->statsEventWriteByteArray(event, (const uint8_t*)sfPredictionErrorBytes.c_str(),
+                                           sfPredictionErrorBytes.size()); // sf_prediction_errors
+        mStatsDelegate->statsEventWriteInt32(event, globalSlice.first.renderRateBucket);
+        mStatsDelegate->statsEventBuild(event);
+    }
+
     clearGlobalLocked();
 
     return AStatsManager_PULL_SUCCESS;
@@ -161,8 +171,17 @@
     std::lock_guard<std::mutex> lock(mMutex);
 
     std::vector<TimeStatsHelper::TimeStatsLayer const*> dumpStats;
-    for (const auto& ele : mTimeStats.stats) {
-        dumpStats.push_back(&ele.second);
+    uint32_t numLayers = 0;
+    for (const auto& globalSlice : mTimeStats.stats) {
+        numLayers += globalSlice.second.stats.size();
+    }
+
+    dumpStats.reserve(numLayers);
+
+    for (const auto& globalSlice : mTimeStats.stats) {
+        for (const auto& layerSlice : globalSlice.second.stats) {
+            dumpStats.push_back(&layerSlice.second);
+        }
     }
 
     std::sort(dumpStats.begin(), dumpStats.end(),
@@ -208,8 +227,9 @@
         mStatsDelegate->statsEventWriteInt32(event, 0); // total_janky_frames_sf_scheduling
         mStatsDelegate->statsEventWriteInt32(event, 0); // total_jank_frames_sf_prediction_error
         mStatsDelegate->statsEventWriteInt32(event, 0); // total_jank_frames_app_buffer_stuffing
-        mStatsDelegate->statsEventWriteInt32(event, 0); // display_refresh_rate_bucket
-        mStatsDelegate->statsEventWriteInt32(event, 0); // render_rate_bucket
+        mStatsDelegate->statsEventWriteInt32(
+                event, layer->displayRefreshRateBucket); // display_refresh_rate_bucket
+        mStatsDelegate->statsEventWriteInt32(event, layer->renderRateBucket); // render_rate_bucket
         std::string frameRateVoteBytes = frameRateVoteToProtoByteString(0.0, 0, 0);
         mStatsDelegate->statsEventWriteByteArray(event, (const uint8_t*)frameRateVoteBytes.c_str(),
                                                  frameRateVoteBytes.size()); // set_frame_rate_vote
@@ -310,7 +330,7 @@
     ATRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
-    mTimeStats.totalFrames++;
+    mTimeStats.totalFramesLegacy++;
 }
 
 void TimeStats::incrementMissedFrames() {
@@ -319,7 +339,7 @@
     ATRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
-    mTimeStats.missedFrames++;
+    mTimeStats.missedFramesLegacy++;
 }
 
 void TimeStats::incrementClientCompositionFrames() {
@@ -328,7 +348,7 @@
     ATRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
-    mTimeStats.clientCompositionFrames++;
+    mTimeStats.clientCompositionFramesLegacy++;
 }
 
 void TimeStats::incrementClientCompositionReusedFrames() {
@@ -337,7 +357,7 @@
     ATRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
-    mTimeStats.clientCompositionReusedFrames++;
+    mTimeStats.clientCompositionReusedFramesLegacy++;
 }
 
 void TimeStats::incrementRefreshRateSwitches() {
@@ -346,7 +366,7 @@
     ATRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
-    mTimeStats.refreshRateSwitches++;
+    mTimeStats.refreshRateSwitchesLegacy++;
 }
 
 void TimeStats::incrementCompositionStrategyChanges() {
@@ -355,7 +375,7 @@
     ATRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
-    mTimeStats.compositionStrategyChanges++;
+    mTimeStats.compositionStrategyChangesLegacy++;
 }
 
 void TimeStats::recordDisplayEventConnectionCount(int32_t count) {
@@ -364,8 +384,8 @@
     ATRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
-    mTimeStats.displayEventConnectionsCount =
-            std::max(mTimeStats.displayEventConnectionsCount, count);
+    mTimeStats.displayEventConnectionsCountLegacy =
+            std::max(mTimeStats.displayEventConnectionsCountLegacy, count);
 }
 
 static int32_t msBetween(nsecs_t start, nsecs_t end) {
@@ -381,7 +401,7 @@
 
     std::lock_guard<std::mutex> lock(mMutex);
     if (mPowerTime.powerMode == PowerMode::ON) {
-        mTimeStats.frameDuration.insert(msBetween(startTime, endTime));
+        mTimeStats.frameDurationLegacy.insert(msBetween(startTime, endTime));
     }
 }
 
@@ -444,12 +464,22 @@
     return true;
 }
 
-void TimeStats::flushAvailableRecordsToStatsLocked(int32_t layerId) {
+static int32_t clampToSmallestBucket(Fps fps, size_t bucketWidth) {
+    return (fps.getIntValue() / bucketWidth) * bucketWidth;
+}
+
+void TimeStats::flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayRefreshRate,
+                                                   std::optional<Fps> renderRate) {
     ATRACE_CALL();
 
     LayerRecord& layerRecord = mTimeStatsTracker[layerId];
     TimeRecord& prevTimeRecord = layerRecord.prevTimeRecord;
     std::deque<TimeRecord>& timeRecords = layerRecord.timeRecords;
+    const int32_t refreshRateBucket =
+            clampToSmallestBucket(displayRefreshRate, REFRESH_RATE_BUCKET_WIDTH);
+    const int32_t renderRateBucket =
+            clampToSmallestBucket(renderRate ? *renderRate : displayRefreshRate,
+                                  RENDER_RATE_BUCKET_WIDTH);
     while (!timeRecords.empty()) {
         if (!recordReadyLocked(layerId, &timeRecords[0])) break;
         ALOGV("[%d]-[%" PRIu64 "]-presentFenceTime[%" PRId64 "]", layerId,
@@ -458,11 +488,21 @@
         if (prevTimeRecord.ready) {
             uid_t uid = layerRecord.uid;
             const std::string& layerName = layerRecord.layerName;
-            if (!mTimeStats.stats.count({uid, layerName})) {
-                mTimeStats.stats[{uid, layerName}].uid = uid;
-                mTimeStats.stats[{uid, layerName}].layerName = layerName;
+            TimeStatsHelper::TimelineStatsKey timelineKey = {refreshRateBucket, renderRateBucket};
+            if (!mTimeStats.stats.count(timelineKey)) {
+                mTimeStats.stats[timelineKey].key = timelineKey;
             }
-            TimeStatsHelper::TimeStatsLayer& timeStatsLayer = mTimeStats.stats[{uid, layerName}];
+
+            TimeStatsHelper::TimelineStats& displayStats = mTimeStats.stats[timelineKey];
+
+            TimeStatsHelper::LayerStatsKey layerKey = {uid, layerName};
+            if (!displayStats.stats.count(layerKey)) {
+                displayStats.stats[layerKey].displayRefreshRateBucket = refreshRateBucket;
+                displayStats.stats[layerKey].renderRateBucket = renderRateBucket;
+                displayStats.stats[layerKey].uid = uid;
+                displayStats.stats[layerKey].layerName = layerName;
+            }
+            TimeStatsHelper::TimeStatsLayer& timeStatsLayer = displayStats.stats[layerKey];
             timeStatsLayer.totalFrames++;
             timeStatsLayer.droppedFrames += layerRecord.droppedFrames;
             timeStatsLayer.lateAcquireFrames += layerRecord.lateAcquireFrames;
@@ -524,8 +564,16 @@
 }
 
 bool TimeStats::canAddNewAggregatedStats(uid_t uid, const std::string& layerName) {
-    return mTimeStats.stats.count({uid, layerName}) > 0 ||
-            mTimeStats.stats.size() < MAX_NUM_LAYER_STATS;
+    uint32_t layerRecords = 0;
+    for (const auto& record : mTimeStats.stats) {
+        if (record.second.stats.count({uid, layerName}) > 0) {
+            return true;
+        }
+
+        layerRecords += record.second.stats.size();
+    }
+
+    return mTimeStats.stats.size() < MAX_NUM_LAYER_STATS;
 }
 
 void TimeStats::setPostTime(int32_t layerId, uint64_t frameNumber, const std::string& layerName,
@@ -676,7 +724,8 @@
     }
 }
 
-void TimeStats::setPresentTime(int32_t layerId, uint64_t frameNumber, nsecs_t presentTime) {
+void TimeStats::setPresentTime(int32_t layerId, uint64_t frameNumber, nsecs_t presentTime,
+                               Fps displayRefreshRate, std::optional<Fps> renderRate) {
     if (!mEnabled.load()) return;
 
     ATRACE_CALL();
@@ -695,11 +744,12 @@
         layerRecord.waitData++;
     }
 
-    flushAvailableRecordsToStatsLocked(layerId);
+    flushAvailableRecordsToStatsLocked(layerId, displayRefreshRate, renderRate);
 }
 
 void TimeStats::setPresentFence(int32_t layerId, uint64_t frameNumber,
-                                const std::shared_ptr<FenceTime>& presentFence) {
+                                const std::shared_ptr<FenceTime>& presentFence,
+                                Fps displayRefreshRate, std::optional<Fps> renderRate) {
     if (!mEnabled.load()) return;
 
     ATRACE_CALL();
@@ -719,7 +769,7 @@
         layerRecord.waitData++;
     }
 
-    flushAvailableRecordsToStatsLocked(layerId);
+    flushAvailableRecordsToStatsLocked(layerId, displayRefreshRate, renderRate);
 }
 
 template <class T>
@@ -746,16 +796,8 @@
     }
 }
 
-void TimeStats::incrementJankyFrames(int32_t reasons) {
-    if (!mEnabled.load()) return;
-
-    ATRACE_CALL();
-    std::lock_guard<std::mutex> lock(mMutex);
-
-    updateJankPayload<TimeStatsHelper::TimeStatsGlobal>(mTimeStats, reasons);
-}
-
-void TimeStats::incrementJankyFrames(uid_t uid, const std::string& layerName, int32_t reasons) {
+void TimeStats::incrementJankyFrames(Fps refreshRate, std::optional<Fps> renderRate, uid_t uid,
+                                     const std::string& layerName, int32_t reasons) {
     if (!mEnabled.load()) return;
 
     ATRACE_CALL();
@@ -772,16 +814,31 @@
     // TimeStats will flush the first present fence for a layer *before* FrameTimeline does so that
     // the first jank record is not dropped.
 
-    bool useDefaultLayerKey = false;
     static const std::string kDefaultLayerName = "none";
-    if (!mTimeStats.stats.count({uid, layerName})) {
-        mTimeStats.stats[{uid, kDefaultLayerName}].uid = uid;
-        mTimeStats.stats[{uid, kDefaultLayerName}].layerName = kDefaultLayerName;
-        useDefaultLayerKey = true;
+
+    const int32_t refreshRateBucket = clampToSmallestBucket(refreshRate, REFRESH_RATE_BUCKET_WIDTH);
+    const int32_t renderRateBucket =
+            clampToSmallestBucket(renderRate ? *renderRate : refreshRate, RENDER_RATE_BUCKET_WIDTH);
+    const TimeStatsHelper::TimelineStatsKey timelineKey = {refreshRateBucket, renderRateBucket};
+
+    if (!mTimeStats.stats.count(timelineKey)) {
+        mTimeStats.stats[timelineKey].key = timelineKey;
     }
 
-    TimeStatsHelper::TimeStatsLayer& timeStatsLayer =
-            mTimeStats.stats[{uid, useDefaultLayerKey ? kDefaultLayerName : layerName}];
+    TimeStatsHelper::TimelineStats& timelineStats = mTimeStats.stats[timelineKey];
+
+    updateJankPayload<TimeStatsHelper::TimelineStats>(timelineStats, reasons);
+
+    TimeStatsHelper::LayerStatsKey layerKey = {uid, layerName};
+    if (!timelineStats.stats.count(layerKey)) {
+        layerKey = {uid, kDefaultLayerName};
+        timelineStats.stats[layerKey].displayRefreshRateBucket = refreshRateBucket;
+        timelineStats.stats[layerKey].renderRateBucket = renderRateBucket;
+        timelineStats.stats[layerKey].uid = uid;
+        timelineStats.stats[layerKey].layerName = kDefaultLayerName;
+    }
+
+    TimeStatsHelper::TimeStatsLayer& timeStatsLayer = timelineStats.stats[layerKey];
     updateJankPayload<TimeStatsHelper::TimeStatsLayer>(timeStatsLayer, reasons);
 }
 
@@ -823,7 +880,7 @@
 
     switch (mPowerTime.powerMode) {
         case PowerMode::ON:
-            mTimeStats.displayOnTime += elapsedTime;
+            mTimeStats.displayOnTimeLegacy += elapsedTime;
             break;
         case PowerMode::OFF:
         case PowerMode::DOZE:
@@ -852,10 +909,10 @@
 
 void TimeStats::recordRefreshRate(uint32_t fps, nsecs_t duration) {
     std::lock_guard<std::mutex> lock(mMutex);
-    if (mTimeStats.refreshRateStats.count(fps)) {
-        mTimeStats.refreshRateStats[fps] += duration;
+    if (mTimeStats.refreshRateStatsLegacy.count(fps)) {
+        mTimeStats.refreshRateStatsLegacy[fps] += duration;
     } else {
-        mTimeStats.refreshRateStats.insert({fps, duration});
+        mTimeStats.refreshRateStatsLegacy.insert({fps, duration});
     }
 }
 
@@ -881,7 +938,7 @@
                     msBetween(mGlobalRecord.prevPresentTime, curPresentTime);
             ALOGV("Global present2present[%d] prev[%" PRId64 "] curr[%" PRId64 "]",
                   presentToPresentMs, mGlobalRecord.prevPresentTime, curPresentTime);
-            mTimeStats.presentToPresent.insert(presentToPresentMs);
+            mTimeStats.presentToPresentLegacy.insert(presentToPresentMs);
         }
 
         mGlobalRecord.prevPresentTime = curPresentTime;
@@ -908,7 +965,7 @@
         }
 
         const int32_t renderEngineMs = msBetween(duration.startTime, endNs);
-        mTimeStats.renderEngineTiming.insert(renderEngineMs);
+        mTimeStats.renderEngineTimingLegacy.insert(renderEngineMs);
 
         mGlobalRecord.renderEngineDurations.pop_front();
     }
@@ -951,7 +1008,7 @@
 
     std::lock_guard<std::mutex> lock(mMutex);
     mEnabled.store(true);
-    mTimeStats.statsStart = static_cast<int64_t>(std::time(0));
+    mTimeStats.statsStartLegacy = static_cast<int64_t>(std::time(0));
     mPowerTime.prevTime = systemTime();
     ALOGD("Enabled");
 }
@@ -964,7 +1021,7 @@
     std::lock_guard<std::mutex> lock(mMutex);
     flushPowerTimeLocked();
     mEnabled.store(false);
-    mTimeStats.statsEnd = static_cast<int64_t>(std::time(0));
+    mTimeStats.statsEndLegacy = static_cast<int64_t>(std::time(0));
     ALOGD("Disabled");
 }
 
@@ -977,21 +1034,20 @@
 void TimeStats::clearGlobalLocked() {
     ATRACE_CALL();
 
-    mTimeStats.statsStart = (mEnabled.load() ? static_cast<int64_t>(std::time(0)) : 0);
-    mTimeStats.statsEnd = 0;
-    mTimeStats.totalFrames = 0;
-    mTimeStats.missedFrames = 0;
-    mTimeStats.clientCompositionFrames = 0;
-    mTimeStats.clientCompositionReusedFrames = 0;
-    mTimeStats.refreshRateSwitches = 0;
-    mTimeStats.compositionStrategyChanges = 0;
-    mTimeStats.displayEventConnectionsCount = 0;
-    mTimeStats.displayOnTime = 0;
-    mTimeStats.presentToPresent.hist.clear();
-    mTimeStats.frameDuration.hist.clear();
-    mTimeStats.renderEngineTiming.hist.clear();
-    mTimeStats.jankPayload = TimeStatsHelper::JankPayload();
-    mTimeStats.refreshRateStats.clear();
+    mTimeStats.statsStartLegacy = (mEnabled.load() ? static_cast<int64_t>(std::time(0)) : 0);
+    mTimeStats.statsEndLegacy = 0;
+    mTimeStats.totalFramesLegacy = 0;
+    mTimeStats.missedFramesLegacy = 0;
+    mTimeStats.clientCompositionFramesLegacy = 0;
+    mTimeStats.clientCompositionReusedFramesLegacy = 0;
+    mTimeStats.refreshRateSwitchesLegacy = 0;
+    mTimeStats.compositionStrategyChangesLegacy = 0;
+    mTimeStats.displayEventConnectionsCountLegacy = 0;
+    mTimeStats.displayOnTimeLegacy = 0;
+    mTimeStats.presentToPresentLegacy.hist.clear();
+    mTimeStats.frameDurationLegacy.hist.clear();
+    mTimeStats.renderEngineTimingLegacy.hist.clear();
+    mTimeStats.refreshRateStatsLegacy.clear();
     mPowerTime.prevTime = systemTime();
     mGlobalRecord.prevPresentTime = 0;
     mGlobalRecord.presentFences.clear();
@@ -1014,11 +1070,11 @@
     ATRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
-    if (mTimeStats.statsStart == 0) {
+    if (mTimeStats.statsStartLegacy == 0) {
         return;
     }
 
-    mTimeStats.statsEnd = static_cast<int64_t>(std::time(0));
+    mTimeStats.statsEndLegacy = static_cast<int64_t>(std::time(0));
 
     flushPowerTimeLocked();
 
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index df40ef6..e76849f 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -27,12 +27,13 @@
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
+#include <../Fps.h>
+#include <gui/JankInfo.h>
 #include <stats_event.h>
 #include <stats_pull_atom_callback.h>
 #include <statslog.h>
 #include <timestatsproto/TimeStatsHelper.h>
 #include <timestatsproto/TimeStatsProtoHeader.h>
-#include <gui/JankInfo.h>
 #include <ui/FenceTime.h>
 #include <utils/String16.h>
 #include <utils/Vector.h>
@@ -108,23 +109,22 @@
                                  const std::shared_ptr<FenceTime>& acquireFence) = 0;
     // SetPresent{Time, Fence} are not expected to be called in the critical
     // rendering path, as they flush prior fences if those fences have fired.
-    virtual void setPresentTime(int32_t layerId, uint64_t frameNumber, nsecs_t presentTime) = 0;
+    virtual void setPresentTime(int32_t layerId, uint64_t frameNumber, nsecs_t presentTime,
+                                Fps displayRefreshRate, std::optional<Fps> renderRate) = 0;
     virtual void setPresentFence(int32_t layerId, uint64_t frameNumber,
-                                 const std::shared_ptr<FenceTime>& presentFence) = 0;
+                                 const std::shared_ptr<FenceTime>& presentFence,
+                                 Fps displayRefreshRate, std::optional<Fps> renderRate) = 0;
 
-    // Increments janky frames, tracked globally. Because FrameTimeline is the infrastructure
-    // responsible for computing jank in the system, this is expected to be called from
-    // FrameTimeline, rather than directly from SurfaceFlinger or individual layers. If there are no
-    // jank reasons, then total frames are incremented but jank is not, for accurate accounting of
-    // janky frames.
-    virtual void incrementJankyFrames(int32_t reasons) = 0;
-    // Increments janky frames, blamed to the provided {uid, layerName} key, with JankMetadata as
-    // supplementary reasons for the jank. Because FrameTimeline is the infrastructure responsible
-    // for computing jank in the system, this is expected to be called from FrameTimeline, rather
-    // than directly from SurfaceFlinger or individual layers.
-    // If there are no jank reasons, then total frames are incremented but jank is not, for accurate
+    // Increments janky frames, blamed to the provided {refreshRate, renderRate, uid, layerName}
+    // key, with JankMetadata as supplementary reasons for the jank. Because FrameTimeline is the
+    // infrastructure responsible for computing jank in the system, this is expected to be called
+    // from FrameTimeline, rather than directly from SurfaceFlinger or individual layers. If there
+    // are no jank reasons, then total frames are incremented but jank is not, for accurate
     // accounting of janky frames.
-    virtual void incrementJankyFrames(uid_t uid, const std::string& layerName, int32_t reasons) = 0;
+    // When these frame counts are incremented, these are also aggregated into a global reporting
+    // packet to help with data validation and assessing of overall device health.
+    virtual void incrementJankyFrames(Fps refreshRate, std::optional<Fps> renderRate, uid_t uid,
+                                      const std::string& layerName, int32_t reasons) = 0;
     // Clean up the layer record
     virtual void onDestroy(int32_t layerId) = 0;
     // If SF skips or rejects a buffer, remove the corresponding TimeRecord.
@@ -268,11 +268,13 @@
     void setAcquireTime(int32_t layerId, uint64_t frameNumber, nsecs_t acquireTime) override;
     void setAcquireFence(int32_t layerId, uint64_t frameNumber,
                          const std::shared_ptr<FenceTime>& acquireFence) override;
-    void setPresentTime(int32_t layerId, uint64_t frameNumber, nsecs_t presentTime) override;
+    void setPresentTime(int32_t layerId, uint64_t frameNumber, nsecs_t presentTime,
+                        Fps displayRefreshRate, std::optional<Fps> renderRate) override;
     void setPresentFence(int32_t layerId, uint64_t frameNumber,
-                         const std::shared_ptr<FenceTime>& presentFence) override;
-    void incrementJankyFrames(int32_t reasons) override;
-    void incrementJankyFrames(uid_t uid, const std::string& layerName, int32_t reasons) override;
+                         const std::shared_ptr<FenceTime>& presentFence, Fps displayRefreshRate,
+                         std::optional<Fps> renderRate) override;
+    void incrementJankyFrames(Fps refreshRate, std::optional<Fps> renderRate, uid_t uid,
+                              const std::string& layerName, int32_t reasons) override;
     // Clean up the layer record
     void onDestroy(int32_t layerId) override;
     // If SF skips or rejects a buffer, remove the corresponding TimeRecord.
@@ -293,7 +295,8 @@
     AStatsManager_PullAtomCallbackReturn populateGlobalAtom(AStatsEventList* data);
     AStatsManager_PullAtomCallbackReturn populateLayerAtom(AStatsEventList* data);
     bool recordReadyLocked(int32_t layerId, TimeRecord* timeRecord);
-    void flushAvailableRecordsToStatsLocked(int32_t layerId);
+    void flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayRefreshRate,
+                                            std::optional<Fps> renderRate);
     void flushPowerTimeLocked();
     void flushAvailableGlobalRecordsToStatsLocked();
     bool canAddNewAggregatedStats(uid_t uid, const std::string& layerName);
@@ -314,6 +317,9 @@
     GlobalRecord mGlobalRecord;
 
     static const size_t MAX_NUM_LAYER_RECORDS = 200;
+
+    static const size_t REFRESH_RATE_BUCKET_WIDTH = 30;
+    static const size_t RENDER_RATE_BUCKET_WIDTH = REFRESH_RATE_BUCKET_WIDTH;
     static const size_t MAX_NUM_LAYER_STATS = 200;
     static const size_t MAX_NUM_PULLED_LAYERS = MAX_NUM_LAYER_STATS;
     std::unique_ptr<StatsEventDelegate> mStatsDelegate = std::make_unique<StatsEventDelegate>();
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
index 0fb748f..df7be1f 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -90,6 +90,8 @@
 
 std::string TimeStatsHelper::TimeStatsLayer::toString() const {
     std::string result = "\n";
+    StringAppendF(&result, "displayRefreshRate = %d fps\n", displayRefreshRateBucket);
+    StringAppendF(&result, "renderRate = %d fps\n", renderRateBucket);
     StringAppendF(&result, "uid = %d\n", uid);
     StringAppendF(&result, "layerName = %s\n", layerName.c_str());
     StringAppendF(&result, "packageName = %s\n", packageName.c_str());
@@ -115,35 +117,45 @@
 
 std::string TimeStatsHelper::TimeStatsGlobal::toString(std::optional<uint32_t> maxLayers) const {
     std::string result = "SurfaceFlinger TimeStats:\n";
-    StringAppendF(&result, "statsStart = %" PRId64 "\n", statsStart);
-    StringAppendF(&result, "statsEnd = %" PRId64 "\n", statsEnd);
-    StringAppendF(&result, "totalFrames = %d\n", totalFrames);
-    StringAppendF(&result, "missedFrames = %d\n", missedFrames);
-    StringAppendF(&result, "clientCompositionFrames = %d\n", clientCompositionFrames);
-    StringAppendF(&result, "clientCompositionReusedFrames = %d\n", clientCompositionReusedFrames);
-    StringAppendF(&result, "refreshRateSwitches = %d\n", refreshRateSwitches);
-    StringAppendF(&result, "compositionStrategyChanges = %d\n", compositionStrategyChanges);
-    StringAppendF(&result, "displayOnTime = %" PRId64 " ms\n", displayOnTime);
-    result.append("Global aggregated jank payload:\n");
-    result.append(jankPayload.toString());
+    result.append("Legacy stats are as follows:\n");
+    StringAppendF(&result, "statsStart = %" PRId64 "\n", statsStartLegacy);
+    StringAppendF(&result, "statsEnd = %" PRId64 "\n", statsEndLegacy);
+    StringAppendF(&result, "totalFrames = %d\n", totalFramesLegacy);
+    StringAppendF(&result, "missedFrames = %d\n", missedFramesLegacy);
+    StringAppendF(&result, "clientCompositionFrames = %d\n", clientCompositionFramesLegacy);
+    StringAppendF(&result, "clientCompositionReusedFrames = %d\n",
+                  clientCompositionReusedFramesLegacy);
+    StringAppendF(&result, "refreshRateSwitches = %d\n", refreshRateSwitchesLegacy);
+    StringAppendF(&result, "compositionStrategyChanges = %d\n", compositionStrategyChangesLegacy);
+    StringAppendF(&result, "displayOnTime = %" PRId64 " ms\n", displayOnTimeLegacy);
     StringAppendF(&result, "displayConfigStats is as below:\n");
-    for (const auto& [fps, duration] : refreshRateStats) {
+    for (const auto& [fps, duration] : refreshRateStatsLegacy) {
         StringAppendF(&result, "%dfps = %ldms\n", fps, ns2ms(duration));
     }
     result.back() = '\n';
-    StringAppendF(&result, "totalP2PTime = %" PRId64 " ms\n", presentToPresent.totalTime());
+    StringAppendF(&result, "totalP2PTime = %" PRId64 " ms\n", presentToPresentLegacy.totalTime());
     StringAppendF(&result, "presentToPresent histogram is as below:\n");
-    result.append(presentToPresent.toString());
-    const float averageFrameDuration = frameDuration.averageTime();
+    result.append(presentToPresentLegacy.toString());
+    const float averageFrameDuration = frameDurationLegacy.averageTime();
     StringAppendF(&result, "averageFrameDuration = %.3f ms\n",
                   std::isnan(averageFrameDuration) ? 0.0f : averageFrameDuration);
     StringAppendF(&result, "frameDuration histogram is as below:\n");
-    result.append(frameDuration.toString());
-    const float averageRenderEngineTiming = renderEngineTiming.averageTime();
+    result.append(frameDurationLegacy.toString());
+    const float averageRenderEngineTiming = renderEngineTimingLegacy.averageTime();
     StringAppendF(&result, "averageRenderEngineTiming = %.3f ms\n",
                   std::isnan(averageRenderEngineTiming) ? 0.0f : averageRenderEngineTiming);
     StringAppendF(&result, "renderEngineTiming histogram is as below:\n");
-    result.append(renderEngineTiming.toString());
+    result.append(renderEngineTimingLegacy.toString());
+
+    result.append("\nGlobal aggregated jank payload (Timeline stats):");
+    for (const auto& ele : stats) {
+        result.append("\n");
+        StringAppendF(&result, "displayRefreshRate = %d fps\n",
+                      ele.second.key.displayRefreshRateBucket);
+        StringAppendF(&result, "renderRate = %d fps\n", ele.second.key.renderRateBucket);
+        result.append(ele.second.jankPayload.toString());
+    }
+
     const auto dumpStats = generateDumpStats(maxLayers);
     for (const auto& ele : dumpStats) {
         result.append(ele->toString());
@@ -173,30 +185,30 @@
 SFTimeStatsGlobalProto TimeStatsHelper::TimeStatsGlobal::toProto(
         std::optional<uint32_t> maxLayers) const {
     SFTimeStatsGlobalProto globalProto;
-    globalProto.set_stats_start(statsStart);
-    globalProto.set_stats_end(statsEnd);
-    globalProto.set_total_frames(totalFrames);
-    globalProto.set_missed_frames(missedFrames);
-    globalProto.set_client_composition_frames(clientCompositionFrames);
-    globalProto.set_display_on_time(displayOnTime);
-    for (const auto& ele : refreshRateStats) {
+    globalProto.set_stats_start(statsStartLegacy);
+    globalProto.set_stats_end(statsEndLegacy);
+    globalProto.set_total_frames(totalFramesLegacy);
+    globalProto.set_missed_frames(missedFramesLegacy);
+    globalProto.set_client_composition_frames(clientCompositionFramesLegacy);
+    globalProto.set_display_on_time(displayOnTimeLegacy);
+    for (const auto& ele : refreshRateStatsLegacy) {
         SFTimeStatsDisplayConfigBucketProto* configBucketProto =
                 globalProto.add_display_config_stats();
         SFTimeStatsDisplayConfigProto* configProto = configBucketProto->mutable_config();
         configProto->set_fps(ele.first);
         configBucketProto->set_duration_millis(ns2ms(ele.second));
     }
-    for (const auto& histEle : presentToPresent.hist) {
+    for (const auto& histEle : presentToPresentLegacy.hist) {
         SFTimeStatsHistogramBucketProto* histProto = globalProto.add_present_to_present();
         histProto->set_time_millis(histEle.first);
         histProto->set_frame_count(histEle.second);
     }
-    for (const auto& histEle : frameDuration.hist) {
+    for (const auto& histEle : frameDurationLegacy.hist) {
         SFTimeStatsHistogramBucketProto* histProto = globalProto.add_frame_duration();
         histProto->set_time_millis(histEle.first);
         histProto->set_frame_count(histEle.second);
     }
-    for (const auto& histEle : renderEngineTiming.hist) {
+    for (const auto& histEle : renderEngineTimingLegacy.hist) {
         SFTimeStatsHistogramBucketProto* histProto = globalProto.add_render_engine_timing();
         histProto->set_time_millis(histEle.first);
         histProto->set_frame_count(histEle.second);
@@ -212,8 +224,18 @@
 std::vector<TimeStatsHelper::TimeStatsLayer const*>
 TimeStatsHelper::TimeStatsGlobal::generateDumpStats(std::optional<uint32_t> maxLayers) const {
     std::vector<TimeStatsLayer const*> dumpStats;
+
+    int numLayers = 0;
     for (const auto& ele : stats) {
-        dumpStats.push_back(&ele.second);
+        numLayers += ele.second.stats.size();
+    }
+
+    dumpStats.reserve(numLayers);
+
+    for (const auto& ele : stats) {
+        for (const auto& layerEle : ele.second.stats) {
+            dumpStats.push_back(&layerEle.second);
+        }
     }
 
     std::sort(dumpStats.begin(), dumpStats.end(),
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
index 033eb5d..0144abc0 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
@@ -57,6 +57,8 @@
         uid_t uid;
         std::string layerName;
         std::string packageName;
+        int32_t displayRefreshRateBucket = 0;
+        int32_t renderRateBucket = 0;
         int32_t totalFrames = 0;
         int32_t droppedFrames = 0;
         int32_t lateAcquireFrames = 0;
@@ -68,32 +70,82 @@
         SFTimeStatsLayerProto toProto() const;
     };
 
-    class TimeStatsGlobal {
-    public:
-        int64_t statsStart = 0;
-        int64_t statsEnd = 0;
-        int32_t totalFrames = 0;
-        int32_t missedFrames = 0;
-        int32_t clientCompositionFrames = 0;
-        int32_t clientCompositionReusedFrames = 0;
-        int32_t refreshRateSwitches = 0;
-        int32_t compositionStrategyChanges = 0;
-        int32_t displayEventConnectionsCount = 0;
-        int64_t displayOnTime = 0;
-        Histogram presentToPresent;
-        Histogram frameDuration;
-        Histogram renderEngineTiming;
+    // Lifted from SkiaGLRenderEngine's LinearEffect class.
+    // Which in turn was inspired by art/runtime/class_linker.cc
+    // Also this is what boost:hash_combine does so this is a pretty good hash.
+    static size_t HashCombine(size_t seed, size_t val) {
+        return seed ^ (val + 0x9e3779b9 + (seed << 6) + (seed >> 2));
+    }
 
-        struct StatsHasher {
-            size_t operator()(const std::pair<uid_t, std::string>& p) const {
-                // Normally this isn't a very good hash function due to symmetry reasons,
-                // but these are distinct types so this should be good enough
-                return std::hash<uid_t>{}(p.first) ^ std::hash<std::string>{}(p.second);
+    struct TimelineStatsKey {
+        int32_t displayRefreshRateBucket = 0;
+        int32_t renderRateBucket = 0;
+
+        struct Hasher {
+            size_t operator()(const TimelineStatsKey& key) const {
+                size_t result = std::hash<int32_t>{}(key.displayRefreshRateBucket);
+                return HashCombine(result, std::hash<int32_t>{}(key.renderRateBucket));
             }
         };
-        std::unordered_map<std::pair<uid_t, std::string>, TimeStatsLayer, StatsHasher> stats;
-        std::unordered_map<uint32_t, nsecs_t> refreshRateStats;
+
+        bool operator==(const TimelineStatsKey& o) const {
+            return displayRefreshRateBucket == o.displayRefreshRateBucket &&
+                    renderRateBucket == o.renderRateBucket;
+        }
+    };
+
+    struct LayerStatsKey {
+        uid_t uid = 0;
+        std::string layerName;
+
+        struct Hasher {
+            size_t operator()(const LayerStatsKey& key) const {
+                size_t result = std::hash<uid_t>{}(key.uid);
+                return HashCombine(result, std::hash<std::string>{}(key.layerName));
+            }
+        };
+
+        bool operator==(const LayerStatsKey& o) const {
+            return uid == o.uid && layerName == o.layerName;
+        }
+    };
+
+    struct LayerStatsHasher {
+        size_t operator()(const std::pair<uid_t, std::string>& p) const {
+            // Normally this isn't a very good hash function due to symmetry reasons,
+            // but these are distinct types so this should be good enough
+            return std::hash<uid_t>{}(p.first) ^ std::hash<std::string>{}(p.second);
+        }
+    };
+
+    struct TimelineStats {
+        TimelineStatsKey key;
         JankPayload jankPayload;
+        std::unordered_map<LayerStatsKey, TimeStatsLayer, LayerStatsKey::Hasher> stats;
+    };
+
+    class TimeStatsGlobal {
+    public:
+        // Note: these are all legacy statistics, we're keeping these around because a variety of
+        // systems and form-factors find these useful when comparing with older releases. However,
+        // the current recommendation is that the new timeline-based metrics are used, and the old
+        // ones are deprecated.
+        int64_t statsStartLegacy = 0;
+        int64_t statsEndLegacy = 0;
+        int32_t totalFramesLegacy = 0;
+        int32_t missedFramesLegacy = 0;
+        int32_t clientCompositionFramesLegacy = 0;
+        int32_t clientCompositionReusedFramesLegacy = 0;
+        int32_t refreshRateSwitchesLegacy = 0;
+        int32_t compositionStrategyChangesLegacy = 0;
+        int32_t displayEventConnectionsCountLegacy = 0;
+        int64_t displayOnTimeLegacy = 0;
+        Histogram presentToPresentLegacy;
+        Histogram frameDurationLegacy;
+        Histogram renderEngineTimingLegacy;
+        std::unordered_map<uint32_t, nsecs_t> refreshRateStatsLegacy;
+
+        std::unordered_map<TimelineStatsKey, TimelineStats, TimelineStatsKey::Hasher> stats;
 
         std::string toString(std::optional<uint32_t> maxLayers) const;
         SFTimeStatsGlobalProto toProto(std::optional<uint32_t> maxLayers) const;