Game Driver: add GpuStats class to process and dump stats

Bug: 123529932
Test: adb shell dumpsys gpu
Change-Id: I2d524b1eecb00be71d344c07e8e18244a44bbcb8
diff --git a/libs/graphicsenv/include/graphicsenv/GpuStatsAtoms.h b/libs/graphicsenv/include/graphicsenv/GpuStatsAtoms.h
new file mode 100644
index 0000000..f8b0ad7
--- /dev/null
+++ b/libs/graphicsenv/include/graphicsenv/GpuStatsAtoms.h
@@ -0,0 +1,42 @@
+/*
+ * 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 <string>
+#include <vector>
+
+namespace android {
+
+struct GpuStatsGlobalAtom {
+    std::string driverPackageName = "";
+    std::string driverVersionName = "";
+    uint64_t driverVersionCode = 0;
+    int64_t driverBuildTime = 0;
+    int32_t glLoadingCount = 0;
+    int32_t glLoadingFailureCount = 0;
+    int32_t vkLoadingCount = 0;
+    int32_t vkLoadingFailureCount = 0;
+};
+
+struct GpuStatsAppAtom {
+    std::string appPackageName = "";
+    uint64_t driverVersionCode = 0;
+    std::vector<int64_t> glDriverLoadingTime = {};
+    std::vector<int64_t> vkDriverLoadingTime = {};
+};
+
+} // namespace android
diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp
index 2e8571a..dbb6ba6 100644
--- a/services/gpuservice/Android.bp
+++ b/services/gpuservice/Android.bp
@@ -2,6 +2,7 @@
     name: "gpuservice_sources",
     srcs: [
         "GpuService.cpp",
+        "gpustats/GpuStats.cpp"
     ],
 }
 
diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp
index 70dd904..d7696f9 100644
--- a/services/gpuservice/GpuService.cpp
+++ b/services/gpuservice/GpuService.cpp
@@ -29,6 +29,8 @@
 
 #include <vkjson.h>
 
+#include "gpustats/GpuStats.h"
+
 namespace android {
 
 using base::StringAppendF;
@@ -42,7 +44,7 @@
 
 const char* const GpuService::SERVICE_NAME = "gpu";
 
-GpuService::GpuService() = default;
+GpuService::GpuService() : mGpuStats(std::make_unique<GpuStats>()){};
 
 void GpuService::setGpuStats(const std::string& driverPackageName,
                              const std::string& driverVersionName, uint64_t driverVersionCode,
@@ -51,18 +53,8 @@
                              int64_t driverLoadingTime) {
     ATRACE_CALL();
 
-    std::lock_guard<std::mutex> lock(mStateLock);
-    ALOGV("Received:\n"
-          "\tdriverPackageName[%s]\n"
-          "\tdriverVersionName[%s]\n"
-          "\tdriverVersionCode[%" PRIu64 "]\n"
-          "\tdriverBuildTime[%" PRId64 "]\n"
-          "\tappPackageName[%s]\n"
-          "\tdriver[%d]\n"
-          "\tisDriverLoaded[%d]\n"
-          "\tdriverLoadingTime[%" PRId64 "]",
-          driverPackageName.c_str(), driverVersionName.c_str(), driverVersionCode, driverBuildTime,
-          appPackageName.c_str(), static_cast<int32_t>(driver), isDriverLoaded, driverLoadingTime);
+    mGpuStats->insert(driverPackageName, driverVersionName, driverVersionCode, driverBuildTime,
+                      appPackageName, driver, isDriverLoaded, driverLoadingTime);
 }
 
 status_t GpuService::shellCommand(int /*in*/, int out, int err, std::vector<String16>& args) {
@@ -81,7 +73,7 @@
     return BAD_VALUE;
 }
 
-status_t GpuService::doDump(int fd, const Vector<String16>& /*args*/, bool /*asProto*/) {
+status_t GpuService::doDump(int fd, const Vector<String16>& args, bool /*asProto*/) {
     std::string result;
 
     IPCThreadState* ipc = IPCThreadState::self();
@@ -91,7 +83,21 @@
     if ((uid != AID_SHELL) && !PermissionCache::checkPermission(sDump, pid, uid)) {
         StringAppendF(&result, "Permission Denial: can't dump gpu from pid=%d, uid=%d\n", pid, uid);
     } else {
-        result.append("Hello world from dumpsys gpu.\n");
+        bool dumpAll = true;
+        size_t index = 0;
+        size_t numArgs = args.size();
+
+        if (numArgs) {
+            if ((index < numArgs) && (args[index] == String16("--gpustats"))) {
+                index++;
+                mGpuStats->dump(args, &result);
+                dumpAll = false;
+            }
+        }
+
+        if (dumpAll) {
+            mGpuStats->dump(Vector<String16>(), &result);
+        }
     }
 
     write(fd, result.c_str(), result.size());
diff --git a/services/gpuservice/GpuService.h b/services/gpuservice/GpuService.h
index 7216035..0cf48bb 100644
--- a/services/gpuservice/GpuService.h
+++ b/services/gpuservice/GpuService.h
@@ -27,6 +27,8 @@
 
 namespace android {
 
+class GpuStats;
+
 class GpuService : public BnGpuService, public PriorityDumper {
 public:
     static const char* const SERVICE_NAME ANDROID_API;
@@ -66,9 +68,7 @@
     /*
      * Attributes
      */
-
-    // GpuStats access must be protected by mStateLock
-    std::mutex mStateLock;
+    std::unique_ptr<GpuStats> mGpuStats;
 };
 
 } // namespace android
diff --git a/services/gpuservice/gpustats/GpuStats.cpp b/services/gpuservice/gpustats/GpuStats.cpp
new file mode 100644
index 0000000..43c9492
--- /dev/null
+++ b/services/gpuservice/gpustats/GpuStats.cpp
@@ -0,0 +1,213 @@
+/*
+ * 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 "GpuStats"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "GpuStats.h"
+
+#include <android-base/stringprintf.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+#include <unordered_set>
+
+namespace android {
+
+using base::StringAppendF;
+
+static bool addLoadingCount(GraphicsEnv::Driver driver, bool isDriverLoaded,
+                            GpuStatsGlobalAtom* const outGlobalAtom) {
+    switch (driver) {
+        case GraphicsEnv::Driver::GL:
+        case GraphicsEnv::Driver::GL_UPDATED:
+            outGlobalAtom->glLoadingCount++;
+            if (!isDriverLoaded) outGlobalAtom->glLoadingFailureCount++;
+            break;
+        case GraphicsEnv::Driver::VULKAN:
+        case GraphicsEnv::Driver::VULKAN_UPDATED:
+            outGlobalAtom->vkLoadingCount++;
+            if (!isDriverLoaded) outGlobalAtom->vkLoadingFailureCount++;
+            break;
+        default:
+            // Currently we don't support GraphicsEnv::Driver::ANGLE because the
+            // basic driver package info only belongs to system or updated driver.
+            return false;
+    }
+
+    return true;
+}
+
+static void addLoadingTime(GraphicsEnv::Driver driver, int64_t driverLoadingTime,
+                           GpuStatsAppAtom* const outAppAtom) {
+    switch (driver) {
+        case GraphicsEnv::Driver::GL:
+        case GraphicsEnv::Driver::GL_UPDATED:
+            outAppAtom->glDriverLoadingTime.emplace_back(driverLoadingTime);
+            break;
+        case GraphicsEnv::Driver::VULKAN:
+        case GraphicsEnv::Driver::VULKAN_UPDATED:
+            outAppAtom->vkDriverLoadingTime.emplace_back(driverLoadingTime);
+            break;
+        default:
+            break;
+    }
+}
+
+void GpuStats::insert(const std::string& driverPackageName, const std::string& driverVersionName,
+                      uint64_t driverVersionCode, int64_t driverBuildTime,
+                      const std::string& appPackageName, GraphicsEnv::Driver driver,
+                      bool isDriverLoaded, int64_t driverLoadingTime) {
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mLock);
+    ALOGV("Received:\n"
+          "\tdriverPackageName[%s]\n"
+          "\tdriverVersionName[%s]\n"
+          "\tdriverVersionCode[%" PRIu64 "]\n"
+          "\tdriverBuildTime[%" PRId64 "]\n"
+          "\tappPackageName[%s]\n"
+          "\tdriver[%d]\n"
+          "\tisDriverLoaded[%d]\n"
+          "\tdriverLoadingTime[%" PRId64 "]",
+          driverPackageName.c_str(), driverVersionName.c_str(), driverVersionCode, driverBuildTime,
+          appPackageName.c_str(), static_cast<int32_t>(driver), isDriverLoaded, driverLoadingTime);
+
+    if (!mGlobalStats.count(driverVersionCode)) {
+        GpuStatsGlobalAtom globalAtom;
+        if (!addLoadingCount(driver, isDriverLoaded, &globalAtom)) {
+            return;
+        }
+        globalAtom.driverPackageName = driverPackageName;
+        globalAtom.driverVersionName = driverVersionName;
+        globalAtom.driverVersionCode = driverVersionCode;
+        globalAtom.driverBuildTime = driverBuildTime;
+        mGlobalStats.insert({driverVersionCode, globalAtom});
+    } else if (!addLoadingCount(driver, isDriverLoaded, &mGlobalStats[driverVersionCode])) {
+        return;
+    }
+
+    if (mAppStats.size() >= MAX_NUM_APP_RECORDS) {
+        ALOGV("GpuStatsAppAtom has reached maximum size. Ignore new stats.");
+        return;
+    }
+
+    const std::string appStatsKey = appPackageName + std::to_string(driverVersionCode);
+    if (!mAppStats.count(appStatsKey)) {
+        GpuStatsAppAtom appAtom;
+        addLoadingTime(driver, driverLoadingTime, &appAtom);
+        appAtom.appPackageName = appPackageName;
+        appAtom.driverVersionCode = driverVersionCode;
+        mAppStats.insert({appStatsKey, appAtom});
+        return;
+    }
+
+    addLoadingTime(driver, driverLoadingTime, &mAppStats[appStatsKey]);
+}
+
+void GpuStats::dump(const Vector<String16>& args, std::string* result) {
+    ATRACE_CALL();
+
+    if (!result) {
+        ALOGE("Dump result shouldn't be nullptr.");
+        return;
+    }
+
+    std::lock_guard<std::mutex> lock(mLock);
+    bool dumpAll = true;
+
+    std::unordered_set<std::string> argsSet;
+    for (size_t i = 0; i < args.size(); i++) {
+        argsSet.insert(String8(args[i]).c_str());
+    }
+
+    const bool dumpGlobal = argsSet.count("--global") != 0;
+    if (dumpGlobal) {
+        dumpGlobalLocked(result);
+        dumpAll = false;
+    }
+
+    const bool dumpApp = argsSet.count("--app") != 0;
+    if (dumpApp) {
+        dumpAppLocked(result);
+        dumpAll = false;
+    }
+
+    if (argsSet.count("--clear")) {
+        bool clearAll = true;
+
+        if (dumpGlobal) {
+            mGlobalStats.clear();
+            clearAll = false;
+        }
+
+        if (dumpApp) {
+            mAppStats.clear();
+            clearAll = false;
+        }
+
+        if (clearAll) {
+            mGlobalStats.clear();
+            mAppStats.clear();
+        }
+
+        dumpAll = false;
+    }
+
+    if (dumpAll) {
+        dumpGlobalLocked(result);
+        dumpAppLocked(result);
+    }
+}
+
+void GpuStats::dumpGlobalLocked(std::string* result) {
+    result->append("GpuStats global:\n");
+
+    for (const auto& ele : mGlobalStats) {
+        StringAppendF(result, "  driverPackageName = %s\n", ele.second.driverPackageName.c_str());
+        StringAppendF(result, "  driverVersionName = %s\n", ele.second.driverVersionName.c_str());
+        StringAppendF(result, "  driverVersionCode = %" PRIu64 "\n", ele.second.driverVersionCode);
+        StringAppendF(result, "  driverBuildTime = %" PRId64 "\n", ele.second.driverBuildTime);
+        StringAppendF(result, "  glLoadingCount = %d\n", ele.second.glLoadingCount);
+        StringAppendF(result, "  glLoadingFailureCount = %d\n", ele.second.glLoadingFailureCount);
+        StringAppendF(result, "  vkLoadingCount = %d\n", ele.second.vkLoadingCount);
+        StringAppendF(result, "  vkLoadingFailureCount = %d\n", ele.second.vkLoadingFailureCount);
+        result->append("\n");
+    }
+}
+
+void GpuStats::dumpAppLocked(std::string* result) {
+    result->append("GpuStats app:\n");
+
+    for (const auto& ele : mAppStats) {
+        StringAppendF(result, "  appPackageName = %s\n", ele.second.appPackageName.c_str());
+        StringAppendF(result, "  driverVersionCode = %" PRIu64 "\n", ele.second.driverVersionCode);
+
+        result->append("  glDriverLoadingTime:");
+        for (int32_t loadingTime : ele.second.glDriverLoadingTime) {
+            StringAppendF(result, " %d", loadingTime);
+        }
+        result->append("\n");
+
+        result->append("  vkDriverLoadingTime:");
+        for (int32_t loadingTime : ele.second.vkDriverLoadingTime) {
+            StringAppendF(result, " %d", loadingTime);
+        }
+        result->append("\n\n");
+    }
+}
+
+} // namespace android
diff --git a/services/gpuservice/gpustats/GpuStats.h b/services/gpuservice/gpustats/GpuStats.h
new file mode 100644
index 0000000..8837c39
--- /dev/null
+++ b/services/gpuservice/gpustats/GpuStats.h
@@ -0,0 +1,60 @@
+/*
+ * 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 <mutex>
+#include <unordered_map>
+#include <vector>
+
+#include <graphicsenv/GpuStatsAtoms.h>
+#include <graphicsenv/GraphicsEnv.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+class GpuStats {
+public:
+    GpuStats() = default;
+    ~GpuStats() = default;
+
+    // Insert new gpu stats into global stats and app stats.
+    void insert(const std::string& driverPackageName, const std::string& driverVersionName,
+                uint64_t driverVersionCode, int64_t driverBuildTime,
+                const std::string& appPackageName, GraphicsEnv::Driver driver, bool isDriverLoaded,
+                int64_t driverLoadingTime);
+    // dumpsys interface
+    void dump(const Vector<String16>& args, std::string* result);
+
+private:
+    // Dump global stats
+    void dumpGlobalLocked(std::string* result);
+    // Dump app stats
+    void dumpAppLocked(std::string* result);
+
+    // This limits the memory usage of GpuStats to be less than 30KB. This is
+    // the maximum atom size statsd could afford.
+    static const size_t MAX_NUM_APP_RECORDS = 300;
+    // GpuStats access should be guarded by mLock.
+    std::mutex mLock;
+    // Key is driver version code.
+    std::unordered_map<uint64_t, GpuStatsGlobalAtom> mGlobalStats;
+    // Key is <app package name>+<driver version code>.
+    std::unordered_map<std::string, GpuStatsAppAtom> mAppStats;
+};
+
+} // namespace android