Print detailed memory usage of Skia for dumpsys gfxinfo

Bug: 74435803
Test: adb shell dumpsys gfxinfo [package_name]
Change-Id: I1f2bcab500fb47c5e0b50c7459d4a876b063916b
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 35790b6..592a6e6 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -168,6 +168,7 @@
         "pipeline/skia/ReorderBarrierDrawables.cpp",
         "pipeline/skia/ShaderCache.cpp",
         "pipeline/skia/SkiaDisplayList.cpp",
+        "pipeline/skia/SkiaMemoryTracer.cpp",
         "pipeline/skia/SkiaOpenGLPipeline.cpp",
         "pipeline/skia/SkiaOpenGLReadback.cpp",
         "pipeline/skia/SkiaPipeline.cpp",
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
new file mode 100644
index 0000000..ee99622
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2018 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 "SkiaMemoryTracer.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+SkiaMemoryTracer::SkiaMemoryTracer(std::vector<ResourcePair> resourceMap, bool itemizeType)
+            : mResourceMap(resourceMap)
+            , mItemizeType(itemizeType)
+            , mTotalSize("bytes", 0)
+            , mPurgeableSize("bytes", 0) {}
+
+SkiaMemoryTracer::SkiaMemoryTracer(const char* categoryKey, bool itemizeType)
+            : mCategoryKey(categoryKey)
+            , mItemizeType(itemizeType)
+            , mTotalSize("bytes", 0)
+            , mPurgeableSize("bytes", 0) {}
+
+const char* SkiaMemoryTracer::mapName(const char* resourceName) {
+    for (auto& resource : mResourceMap) {
+        if (SkStrContains(resourceName, resource.first)) {
+            return resource.second;
+        }
+    }
+    return nullptr;
+}
+
+void SkiaMemoryTracer::processElement() {
+    if(!mCurrentElement.empty()) {
+        // Only count elements that contain "size", other values just provide metadata.
+        auto sizeResult = mCurrentValues.find("size");
+        if (sizeResult != mCurrentValues.end()) {
+            mTotalSize.value += sizeResult->second.value;
+            mTotalSize.count++;
+        } else {
+            mCurrentElement.clear();
+            mCurrentValues.clear();
+            return;
+        }
+
+        // find the purgeable size if one exists
+        auto purgeableResult = mCurrentValues.find("purgeable_size");
+        if (purgeableResult != mCurrentValues.end()) {
+            mPurgeableSize.value += purgeableResult->second.value;
+            mPurgeableSize.count++;
+        }
+
+        // find the type if one exists
+        const char* type;
+        auto typeResult = mCurrentValues.find("type");
+        if (typeResult != mCurrentValues.end()) {
+            type = typeResult->second.units;
+        } else if (mItemizeType) {
+            type = "Other";
+        }
+
+        // compute the type if we are itemizing or use the default "size" if we are not
+        const char* key = (mItemizeType) ? type : sizeResult->first;
+        SkASSERT(key != nullptr);
+
+        // compute the top level element name using either the map or category key
+        const char* resourceName = mapName(mCurrentElement.c_str());
+        if (mCategoryKey != nullptr) {
+            // find the category if one exists
+            auto categoryResult = mCurrentValues.find(mCategoryKey);
+            if (categoryResult != mCurrentValues.end()) {
+                resourceName = categoryResult->second.units;
+            } else if (mItemizeType) {
+                resourceName = "Other";
+            }
+        }
+
+        // if we don't have a resource name then we don't know how to label the
+        // data and should abort.
+        if (resourceName == nullptr) {
+            mCurrentElement.clear();
+            mCurrentValues.clear();
+            return;
+        }
+
+        auto result = mResults.find(resourceName);
+        if (result != mResults.end()) {
+            auto& resourceValues = result->second;
+            typeResult = resourceValues.find(key);
+            if (typeResult != resourceValues.end()) {
+                SkASSERT(sizeResult->second.units == typeResult->second.units);
+                typeResult->second.value += sizeResult->second.value;
+                typeResult->second.count++;
+            } else {
+                resourceValues.insert({key, sizeResult->second});
+            }
+        } else {
+            mCurrentValues.clear();
+            mCurrentValues.insert({key, sizeResult->second});
+            mResults.insert({resourceName, mCurrentValues});
+        }
+    }
+
+    mCurrentElement.clear();
+    mCurrentValues.clear();
+}
+
+void SkiaMemoryTracer::dumpNumericValue(const char* dumpName, const char* valueName,
+                                        const char* units, uint64_t value) {
+    if (mCurrentElement != dumpName) {
+        processElement();
+        mCurrentElement = dumpName;
+    }
+    mCurrentValues.insert({valueName, {units, value}});
+}
+
+void SkiaMemoryTracer::logOutput(String8& log) {
+    // process any remaining elements
+    processElement();
+
+    for (const auto& namedItem : mResults) {
+        if (mItemizeType) {
+            log.appendFormat("  %s:\n", namedItem.first.c_str());
+            for (const auto& typedValue : namedItem.second) {
+                TraceValue traceValue = convertUnits(typedValue.second);
+                const char* entry = (traceValue.count > 1) ? "entries" : "entry";
+                log.appendFormat("    %s: %.2f %s (%d %s)\n", typedValue.first,
+                                 traceValue.value, traceValue.units, traceValue.count, entry);
+            }
+        } else {
+            auto result = namedItem.second.find("size");
+            if (result != namedItem.second.end()) {
+                TraceValue traceValue = convertUnits(result->second);
+                const char* entry = (traceValue.count > 1) ? "entries" : "entry";
+                log.appendFormat("  %s: %.2f %s (%d %s)\n", namedItem.first.c_str(),
+                                 traceValue.value, traceValue.units, traceValue.count, entry);
+            }
+        }
+    }
+}
+
+void SkiaMemoryTracer::logTotals(String8& log) {
+    TraceValue total = convertUnits(mTotalSize);
+    TraceValue purgeable = convertUnits(mPurgeableSize);
+    log.appendFormat("  %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
+                     total.value, total.units, purgeable.value, purgeable.units);
+}
+
+SkiaMemoryTracer::TraceValue SkiaMemoryTracer::convertUnits(const TraceValue& value) {
+    TraceValue output(value);
+    if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+        output.value = output.value / 1024.0f;
+        output.units = "KB";
+    }
+    if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+        output.value = output.value / 1024.0f;
+        output.units = "MB";
+    }
+    return output;
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
new file mode 100644
index 0000000..abf1f4b
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 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 <SkString.h>
+#include <SkTraceMemoryDump.h>
+#include <utils/String8.h>
+#include <unordered_map>
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+typedef std::pair<const char*, const char*> ResourcePair;
+
+class SkiaMemoryTracer : public SkTraceMemoryDump {
+public:
+    SkiaMemoryTracer(std::vector<ResourcePair> resourceMap, bool itemizeType);
+    SkiaMemoryTracer(const char* categoryKey, bool itemizeType);
+    ~SkiaMemoryTracer() override {}
+
+    void logOutput(String8& log);
+    void logTotals(String8& log);
+
+    void dumpNumericValue(const char* dumpName, const char* valueName, const char* units,
+                          uint64_t value) override;
+
+    void dumpStringValue(const char* dumpName, const char* valueName, const char* value) override {
+        // for convenience we just store this in the same format as numerical values
+        dumpNumericValue(dumpName, valueName, value, 0);
+    }
+
+    LevelOfDetail getRequestedDetails() const override {
+        return SkTraceMemoryDump::kLight_LevelOfDetail;
+    }
+
+    bool shouldDumpWrappedObjects() const override { return true; }
+    void setMemoryBacking(const char*, const char*, const char*) override { }
+    void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override { }
+
+private:
+    struct TraceValue {
+        TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
+        TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
+
+        const char* units;
+        float value;
+        int count;
+    };
+
+    const char* mapName(const char* resourceName);
+    void processElement();
+    TraceValue convertUnits(const TraceValue& value);
+
+    const std::vector<ResourcePair> mResourceMap;
+    const char* mCategoryKey = nullptr;
+    const bool mItemizeType;
+
+    // variables storing the size of all elements being dumped
+    TraceValue mTotalSize;
+    TraceValue mPurgeableSize;
+
+    // variables storing information on the current node being dumped
+    std::string mCurrentElement;
+    std::unordered_map<const char*, TraceValue> mCurrentValues;
+
+    // variable that stores the final format of the data after the individual elements are processed
+    std::unordered_map<std::string, std::unordered_map<const char*, TraceValue>> mResults;
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 907f2d2..32d4c77 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -20,10 +20,12 @@
 #include "Properties.h"
 #include "RenderThread.h"
 #include "pipeline/skia/ShaderCache.h"
+#include "pipeline/skia/SkiaMemoryTracer.h"
 #include "renderstate/RenderState.h"
 
 #include <GrContextOptions.h>
 #include <SkExecutor.h>
+#include <SkGraphics.h>
 #include <gui/Surface.h>
 #include <math.h>
 #include <set>
@@ -178,12 +180,29 @@
         return;
     }
 
-    size_t bytesCached;
-    mGrContext->getResourceCacheUsage(nullptr, &bytesCached);
+    log.appendFormat("Font Cache (CPU):\n");
+    log.appendFormat("  Size: %.2f kB \n", SkGraphics::GetFontCacheUsed() / 1024.0f);
+    log.appendFormat("  Glyph Count: %d \n", SkGraphics::GetFontCacheCountUsed());
 
-    log.appendFormat("Caches:\n");
+    log.appendFormat("CPU Caches:\n");
+    std::vector<skiapipeline::ResourcePair> cpuResourceMap = {
+            {"skia/sk_resource_cache/bitmap_", "Bitmaps"},
+            {"skia/sk_resource_cache/rrect-blur_", "Masks"},
+            {"skia/sk_resource_cache/rects-blur_", "Masks"},
+            {"skia/sk_resource_cache/tessellated", "Shadows"},
+    };
+    skiapipeline::SkiaMemoryTracer cpuTracer(cpuResourceMap, false);
+    SkGraphics::DumpMemoryStatistics(&cpuTracer);
+    cpuTracer.logOutput(log);
+
+    log.appendFormat("GPU Caches:\n");
+    skiapipeline::SkiaMemoryTracer gpuTracer("category", true);
+    mGrContext->dumpMemoryStatistics(&gpuTracer);
+    gpuTracer.logOutput(log);
+
+    log.appendFormat("Other Caches:\n");
     log.appendFormat("                         Current / Maximum\n");
-    log.appendFormat("  VectorDrawableAtlas  %6.2f kB / %6.2f kB (entries = %zu)\n", 0.0f, 0.0f,
+    log.appendFormat("  VectorDrawableAtlas  %6.2f kB / %6.2f KB (entries = %zu)\n", 0.0f, 0.0f,
                      (size_t)0);
 
     if (renderState) {
@@ -200,14 +219,12 @@
                              layer->getHeight());
             layerMemoryTotal += layer->getWidth() * layer->getHeight() * 4;
         }
-        log.appendFormat("  Layers Total         %6.2f kB (numLayers = %zu)\n",
+        log.appendFormat("  Layers Total         %6.2f KB (numLayers = %zu)\n",
                          layerMemoryTotal / 1024.0f, renderState->mActiveLayers.size());
     }
 
-    log.appendFormat("Total memory usage:\n");
-    log.appendFormat("  %zu bytes, %.2f MB (%.2f MB is purgeable)\n", bytesCached,
-                     bytesCached / 1024.0f / 1024.0f,
-                     mGrContext->getResourceCachePurgeableBytes() / 1024.0f / 1024.0f);
+    log.appendFormat("Total GPU memory usage:\n");
+    gpuTracer.logTotals(log);
 }
 
 } /* namespace renderthread */