[TimeStats] Add callback for layer stats

Bug: 119885568
Test: adb shell cmd stats pull-source 10063
Change-Id: I385c9a49e2c7f98d4dacba6ac399e927c5b0e192
diff --git a/services/surfaceflinger/TimeStats/Android.bp b/services/surfaceflinger/TimeStats/Android.bp
index 7ff2594..d27fbb4 100644
--- a/services/surfaceflinger/TimeStats/Android.bp
+++ b/services/surfaceflinger/TimeStats/Android.bp
@@ -8,6 +8,7 @@
         "libcutils",
         "liblog",
         "libprotobuf-cpp-lite",
+        "libprotoutil",
         "libstatslog",
         "libstatspull",
         "libstatssocket",
@@ -17,6 +18,7 @@
     ],
     export_include_dirs: ["."],
     export_shared_lib_headers: [
+        "libprotoutil",
         "libstatslog",
         "libstatspull",
         "libstatssocket",
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 44a59fd..7c824ec 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -24,6 +24,7 @@
 #include "TimeStats.h"
 
 #include <android-base/stringprintf.h>
+#include <android/util/ProtoOutputStream.h>
 #include <log/log.h>
 #include <utils/String8.h>
 #include <utils/Timers.h>
@@ -36,42 +37,134 @@
 
 namespace impl {
 
-status_pull_atom_return_t TimeStats::pullGlobalAtomCallback(int32_t atom_tag,
-                                                            pulled_stats_event_list* data,
-                                                            void* cookie) {
+status_pull_atom_return_t TimeStats::pullAtomCallback(int32_t atom_tag,
+                                                      pulled_stats_event_list* data, void* cookie) {
     impl::TimeStats* timeStats = reinterpret_cast<impl::TimeStats*>(cookie);
-    if (atom_tag != android::util::SURFACEFLINGER_STATS_GLOBAL_INFO) {
-        return STATS_PULL_SKIP;
+    if (atom_tag == android::util::SURFACEFLINGER_STATS_GLOBAL_INFO) {
+        return timeStats->populateGlobalAtom(data);
+    } else if (atom_tag == android::util::SURFACEFLINGER_STATS_LAYER_INFO) {
+        return timeStats->populateLayerAtom(data);
     }
 
-    std::lock_guard<std::mutex> lock(timeStats->mMutex);
+    return STATS_PULL_SKIP;
+}
 
-    const auto& stats = timeStats->mTimeStats;
-    if (stats.statsStart == 0) {
+status_pull_atom_return_t TimeStats::populateGlobalAtom(pulled_stats_event_list* data) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    if (mTimeStats.statsStart == 0) {
         return STATS_PULL_SKIP;
     }
-    timeStats->flushPowerTimeLocked();
+    flushPowerTimeLocked();
 
-    struct stats_event* event = timeStats->mStatsDelegate->addStatsEventToPullData(data);
-    timeStats->mStatsDelegate->statsEventSetAtomId(event,
-                                                   android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
-    timeStats->mStatsDelegate->statsEventWriteInt64(event, stats.totalFrames);
-    timeStats->mStatsDelegate->statsEventWriteInt64(event, stats.missedFrames);
-    timeStats->mStatsDelegate->statsEventWriteInt64(event, stats.clientCompositionFrames);
-    timeStats->mStatsDelegate->statsEventWriteInt64(event, stats.displayOnTime);
-    timeStats->mStatsDelegate->statsEventWriteInt64(event, stats.presentToPresent.totalTime());
-    timeStats->mStatsDelegate->statsEventBuild(event);
-    timeStats->clearGlobalLocked();
+    struct stats_event* 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->statsEventBuild(event);
+    clearGlobalLocked();
 
     return STATS_PULL_SUCCESS;
 }
 
-TimeStats::TimeStats() : TimeStats(nullptr) {}
+namespace {
+// Histograms align with the order of fields in SurfaceflingerStatsLayerInfo.
+const std::array<std::string, 6> kHistogramNames = {
+        "present2present", "post2present",    "acquire2present",
+        "latch2present",   "desired2present", "post2acquire",
+};
 
-TimeStats::TimeStats(std::unique_ptr<StatsEventDelegate> statsDelegate) {
+std::string histogramToProtoByteString(const std::unordered_map<int32_t, int32_t>& histogram,
+                                       size_t maxPulledHistogramBuckets) {
+    auto buckets = std::vector<std::pair<int32_t, int32_t>>(histogram.begin(), histogram.end());
+    std::sort(buckets.begin(), buckets.end(),
+              [](std::pair<int32_t, int32_t>& left, std::pair<int32_t, int32_t>& right) {
+                  return left.second > right.second;
+              });
+
+    util::ProtoOutputStream proto;
+    int histogramSize = 0;
+    for (const auto& bucket : buckets) {
+        if (++histogramSize > maxPulledHistogramBuckets) {
+            break;
+        }
+        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+                            1 /* field id */,
+                    (int32_t)bucket.first);
+        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+                            2 /* field id */,
+                    (int64_t)bucket.second);
+    }
+
+    std::string byteString;
+    proto.serializeToString(&byteString);
+    return byteString;
+}
+} // namespace
+
+status_pull_atom_return_t TimeStats::populateLayerAtom(pulled_stats_event_list* data) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    std::vector<TimeStatsHelper::TimeStatsLayer const*> dumpStats;
+    for (const auto& ele : mTimeStats.stats) {
+        dumpStats.push_back(&ele.second);
+    }
+
+    std::sort(dumpStats.begin(), dumpStats.end(),
+              [](TimeStatsHelper::TimeStatsLayer const* l,
+                 TimeStatsHelper::TimeStatsLayer const* r) {
+                  return l->totalFrames > r->totalFrames;
+              });
+
+    if (mMaxPulledLayers < dumpStats.size()) {
+        dumpStats.resize(mMaxPulledLayers);
+    }
+
+    for (const auto& layer : dumpStats) {
+        struct stats_event* event = mStatsDelegate->addStatsEventToPullData(data);
+        mStatsDelegate->statsEventSetAtomId(event, android::util::SURFACEFLINGER_STATS_LAYER_INFO);
+        mStatsDelegate->statsEventWriteString8(event, layer->layerName.c_str());
+        mStatsDelegate->statsEventWriteInt64(event, layer->totalFrames);
+        mStatsDelegate->statsEventWriteInt64(event, layer->droppedFrames);
+
+        for (const auto& name : kHistogramNames) {
+            const auto& histogram = layer->deltas.find(name);
+            if (histogram == layer->deltas.cend()) {
+                mStatsDelegate->statsEventWriteByteArray(event, nullptr, 0);
+            } else {
+                std::string bytes = histogramToProtoByteString(histogram->second.hist,
+                                                               mMaxPulledHistogramBuckets);
+                mStatsDelegate->statsEventWriteByteArray(event, (const uint8_t*)bytes.c_str(),
+                                                         bytes.size());
+            }
+        }
+
+        mStatsDelegate->statsEventBuild(event);
+    }
+    clearLayersLocked();
+
+    return STATS_PULL_SUCCESS;
+}
+
+TimeStats::TimeStats() : TimeStats(nullptr, std::nullopt, std::nullopt) {}
+
+TimeStats::TimeStats(std::unique_ptr<StatsEventDelegate> statsDelegate,
+                     std::optional<size_t> maxPulledLayers,
+                     std::optional<size_t> maxPulledHistogramBuckets) {
     if (statsDelegate != nullptr) {
         mStatsDelegate = std::move(statsDelegate);
     }
+
+    if (maxPulledLayers) {
+        mMaxPulledLayers = *maxPulledLayers;
+    }
+
+    if (maxPulledHistogramBuckets) {
+        mMaxPulledHistogramBuckets = *maxPulledHistogramBuckets;
+    }
 }
 
 void TimeStats::onBootFinished() {
@@ -634,7 +727,9 @@
     mTimeStats.statsStart = static_cast<int64_t>(std::time(0));
     mPowerTime.prevTime = systemTime();
     mStatsDelegate->registerStatsPullAtomCallback(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
-                                                  TimeStats::pullGlobalAtomCallback, nullptr, this);
+                                                  TimeStats::pullAtomCallback, nullptr, this);
+    mStatsDelegate->registerStatsPullAtomCallback(android::util::SURFACEFLINGER_STATS_LAYER_INFO,
+                                                  TimeStats::pullAtomCallback, nullptr, this);
     ALOGD("Enabled");
 }
 
@@ -649,6 +744,7 @@
     mTimeStats.statsEnd = static_cast<int64_t>(std::time(0));
     mStatsDelegate->unregisterStatsPullAtomCallback(
             android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
+    mStatsDelegate->unregisterStatsPullAtomCallback(android::util::SURFACEFLINGER_STATS_LAYER_INFO);
     ALOGD("Disabled");
 }
 
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index 5cd421c..cf1c3c6 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -165,10 +165,21 @@
             return stats_event_write_int64(event, field);
         }
 
+        virtual void statsEventWriteString8(struct stats_event* event, const char* field) {
+            return stats_event_write_string8(event, field);
+        }
+
+        virtual void statsEventWriteByteArray(struct stats_event* event, const uint8_t* buf,
+                                              size_t numBytes) {
+            return stats_event_write_byte_array(event, buf, numBytes);
+        }
+
         virtual void statsEventBuild(struct stats_event* event) { return stats_event_build(event); }
     };
     // For testing only for injecting custom dependencies.
-    TimeStats(std::unique_ptr<StatsEventDelegate> statsDelegate);
+    TimeStats(std::unique_ptr<StatsEventDelegate> statsDelegate,
+              std::optional<size_t> maxPulledLayers,
+              std::optional<size_t> maxPulledHistogramBuckets);
 
     void onBootFinished() override;
     void parseArgs(bool asProto, const Vector<String16>& args, std::string& result) override;
@@ -207,9 +218,10 @@
     static const size_t MAX_NUM_TIME_RECORDS = 64;
 
 private:
-    static status_pull_atom_return_t pullGlobalAtomCallback(int32_t atom_tag,
-                                                            pulled_stats_event_list* data,
-                                                            void* cookie);
+    static status_pull_atom_return_t pullAtomCallback(int32_t atom_tag,
+                                                      pulled_stats_event_list* data, void* cookie);
+    status_pull_atom_return_t populateGlobalAtom(pulled_stats_event_list* data);
+    status_pull_atom_return_t populateLayerAtom(pulled_stats_event_list* data);
     bool recordReadyLocked(int32_t layerId, TimeRecord* timeRecord);
     void flushAvailableRecordsToStatsLocked(int32_t layerId);
     void flushPowerTimeLocked();
@@ -233,6 +245,8 @@
     static const size_t MAX_NUM_LAYER_RECORDS = 200;
     static const size_t MAX_NUM_LAYER_STATS = 200;
     std::unique_ptr<StatsEventDelegate> mStatsDelegate = std::make_unique<StatsEventDelegate>();
+    size_t mMaxPulledLayers = 8;
+    size_t mMaxPulledHistogramBuckets = 6;
 };
 
 } // namespace impl