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 */