[SfStats] Implement global atom puller for SfStats

Bug: 119885568
Bug: 136597024
Test: adb shell cmd stats pull-source 10062
Test: statsd_testdrive 10062
Test: atest statsd_test
Change-Id: Ib113066faf67f6abba6cd377aaf1fe17cf16d4d0
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 43058d5..6b20aa7 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -74,6 +74,7 @@
         "src/external/StatsPuller.cpp",
         "src/external/StatsPullerManager.cpp",
         "src/external/SubsystemSleepStatePuller.cpp",
+        "src/external/SurfaceflingerStatsPuller.cpp",
         "src/external/TrainInfoPuller.cpp",
         "src/FieldValue.cpp",
         "src/guardrail/StatsdStats.cpp",
@@ -136,6 +137,7 @@
         "libservices",
         "libstatslog",
         "libsysutils",
+        "libtimestats_proto",
         "libutils",
     ],
 }
@@ -237,6 +239,7 @@
         "tests/external/IncidentReportArgs_test.cpp",
         "tests/external/puller_util_test.cpp",
         "tests/external/StatsPuller_test.cpp",
+        "tests/external/SurfaceflingerStatsPuller_test.cpp",
         "tests/FieldValue_test.cpp",
         "tests/guardrail/StatsdStats_test.cpp",
         "tests/indexed_priority_queue_test.cpp",
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 475f18a..4cf7bc4 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -17,12 +17,17 @@
 #define DEBUG false
 #include "Log.h"
 
+#include "StatsPullerManager.h"
+
 #include <android/os/IStatsCompanionService.h>
 #include <android/os/IStatsPullerCallback.h>
 #include <cutils/log.h>
 #include <math.h>
 #include <stdint.h>
+
 #include <algorithm>
+#include <iostream>
+
 #include "../StatsService.h"
 #include "../logd/LogEvent.h"
 #include "../stats_log_util.h"
@@ -32,13 +37,11 @@
 #include "ResourceHealthManagerPuller.h"
 #include "StatsCallbackPuller.h"
 #include "StatsCompanionServicePuller.h"
-#include "StatsPullerManager.h"
 #include "SubsystemSleepStatePuller.h"
+#include "SurfaceflingerStatsPuller.h"
 #include "TrainInfoPuller.h"
 #include "statslog.h"
 
-#include <iostream>
-
 using std::make_shared;
 using std::map;
 using std::shared_ptr;
@@ -266,6 +269,10 @@
         // App ops
         {android::util::APP_OPS,
          {.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}},
+        // SurfaceflingerStatsGlobalInfo
+        {android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
+         {.puller =
+                  new SurfaceflingerStatsPuller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO)}},
 };
 
 StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
new file mode 100644
index 0000000..23b2236
--- /dev/null
+++ b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+#include "SurfaceflingerStatsPuller.h"
+
+#include <cutils/compiler.h>
+
+#include <numeric>
+
+#include "logd/LogEvent.h"
+#include "stats_log_util.h"
+#include "statslog.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+SurfaceflingerStatsPuller::SurfaceflingerStatsPuller(const int tagId) : StatsPuller(tagId) {
+}
+
+bool SurfaceflingerStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) {
+    switch (mTagId) {
+        case android::util::SURFACEFLINGER_STATS_GLOBAL_INFO:
+            return pullGlobalInfo(data);
+        default:
+            break;
+    }
+
+    return false;
+}
+
+static int64_t getTotalTime(
+        const google::protobuf::RepeatedPtrField<surfaceflinger::SFTimeStatsHistogramBucketProto>&
+                buckets) {
+    int64_t total = 0;
+    for (const auto& bucket : buckets) {
+        if (bucket.time_millis() == 1000) {
+            continue;
+        }
+
+        total += bucket.time_millis() * bucket.frame_count();
+    }
+
+    return total;
+}
+
+bool SurfaceflingerStatsPuller::pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data) {
+    std::string protoBytes;
+    if (CC_UNLIKELY(mStatsProvider)) {
+        protoBytes = mStatsProvider();
+    } else {
+        std::unique_ptr<FILE, decltype(&pclose)> pipe(popen("dumpsys SurfaceFlinger --timestats -dump --proto", "r"), pclose);
+        if (!pipe.get()) {
+            return false;
+        }
+        char buf[1024];
+        size_t bytesRead = 0;
+        do {
+            bytesRead = fread(buf, 1, sizeof(buf), pipe.get());
+            protoBytes.append(buf, bytesRead);
+        } while (bytesRead > 0);
+    }
+    surfaceflinger::SFTimeStatsGlobalProto proto;
+    proto.ParseFromString(protoBytes);
+
+    int64_t totalTime = getTotalTime(proto.present_to_present());
+
+    data->clear();
+    data->reserve(1);
+    std::shared_ptr<LogEvent> event =
+            make_shared<LogEvent>(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, getWallClockNs(),
+                                  getElapsedRealtimeNs());
+    if (!event->write(proto.total_frames())) return false;
+    if (!event->write(proto.missed_frames())) return false;
+    if (!event->write(proto.client_composition_frames())) return false;
+    if (!event->write(proto.display_on_time())) return false;
+    if (!event->write(totalTime)) return false;
+    event->init();
+    data->emplace_back(event);
+
+    return true;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
new file mode 100644
index 0000000..ed7153e
--- /dev/null
+++ b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
@@ -0,0 +1,48 @@
+/*
+ * 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 <timestatsproto/TimeStatsProtoHeader.h>
+
+#include "StatsPuller.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Pull metrics from Surfaceflinger
+ */
+class SurfaceflingerStatsPuller : public StatsPuller {
+public:
+    explicit SurfaceflingerStatsPuller(const int tagId);
+
+    // StatsPuller interface
+    bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override;
+
+protected:
+    // Test-only, for injecting fake data
+    using StatsProvider = std::function<std::string()>;
+    StatsProvider mStatsProvider;
+
+private:
+    bool pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
new file mode 100644
index 0000000..5c9636f
--- /dev/null
+++ b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "SurfaceflingerStatsPuller_test"
+
+#include "src/external/SurfaceflingerStatsPuller.h"
+
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class TestableSurfaceflingerStatsPuller : public SurfaceflingerStatsPuller {
+public:
+    TestableSurfaceflingerStatsPuller(const int tagId) : SurfaceflingerStatsPuller(tagId){};
+
+    void injectStats(const StatsProvider& statsProvider) {
+        mStatsProvider = statsProvider;
+    }
+};
+
+class SurfaceflingerStatsPullerTest : public ::testing::Test {
+public:
+    SurfaceflingerStatsPullerTest() {
+        const ::testing::TestInfo* const test_info =
+                ::testing::UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+    }
+
+    ~SurfaceflingerStatsPullerTest() {
+        const ::testing::TestInfo* const test_info =
+                ::testing::UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+    }
+};
+
+TEST_F(SurfaceflingerStatsPullerTest, pullGlobalStats) {
+    surfaceflinger::SFTimeStatsGlobalProto proto;
+    proto.set_total_frames(1);
+    proto.set_missed_frames(2);
+    proto.set_client_composition_frames(2);
+    proto.set_display_on_time(4);
+
+    auto bucketOne = proto.add_present_to_present();
+    bucketOne->set_time_millis(2);
+    bucketOne->set_frame_count(4);
+    auto bucketTwo = proto.add_present_to_present();
+    bucketTwo->set_time_millis(4);
+    bucketTwo->set_frame_count(1);
+    auto bucketThree = proto.add_present_to_present();
+    bucketThree->set_time_millis(1000);
+    bucketThree->set_frame_count(1);
+    static constexpr int64_t expectedAnimationMillis = 12;
+    TestableSurfaceflingerStatsPuller puller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
+
+    puller.injectStats([&] {
+        return proto.SerializeAsString();
+    });
+    puller.ForceClearCache();
+    vector<std::shared_ptr<LogEvent>> outData;
+    puller.Pull(&outData);
+
+    ASSERT_EQ(1, outData.size());
+    EXPECT_EQ(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, outData[0]->GetTagId());
+    EXPECT_EQ(proto.total_frames(), outData[0]->getValues()[0].mValue.long_value);
+    EXPECT_EQ(proto.missed_frames(), outData[0]->getValues()[1].mValue.long_value);
+    EXPECT_EQ(proto.client_composition_frames(), outData[0]->getValues()[2].mValue.long_value);
+    EXPECT_EQ(proto.display_on_time(), outData[0]->getValues()[3].mValue.long_value);
+    EXPECT_EQ(expectedAnimationMillis, outData[0]->getValues()[4].mValue.long_value);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif