/* * Copyright (C) 2021 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 "RenderEngine" #include "SkiaMemoryReporter.h" #include <android-base/stringprintf.h> #include <log/log_main.h> namespace android { namespace renderengine { namespace skia { using base::StringAppendF; SkiaMemoryReporter::SkiaMemoryReporter(const std::vector<ResourcePair>& resourceMap, bool itemize) : mResourceMap(resourceMap), mItemize(itemize), mTotalSize("bytes", 0), mPurgeableSize("bytes", 0) {} const char* SkiaMemoryReporter::mapName(const char* resourceName) { for (auto& resource : mResourceMap) { if (SkStrContains(resourceName, resource.first)) { return resource.second; } } return nullptr; } void SkiaMemoryReporter::resetCurrentElement() { mCurrentElement.clear(); mCurrentValues.clear(); mIsCurrentValueWrapped = false; } void SkiaMemoryReporter::processCurrentElement() { // compute the top level element name using the map const char* resourceName = mCurrentElement.empty() ? nullptr : mapName(mCurrentElement.c_str()); // if we don't have a resource name then we don't know how to label the // data and should abort. if (resourceName == nullptr) { resetCurrentElement(); return; } // Only count elements that contain "size"; other values just provide metadata. auto sizeResult = mCurrentValues.find("size"); if (sizeResult != mCurrentValues.end() && sizeResult->second.value > 0) { if (!mIsCurrentValueWrapped) { mTotalSize.value += sizeResult->second.value; mTotalSize.count++; } } else { resetCurrentElement(); return; } // find the purgeable size if one exists auto purgeableResult = mCurrentValues.find("purgeable_size"); if (!mIsCurrentValueWrapped && purgeableResult != mCurrentValues.end()) { mPurgeableSize.value += purgeableResult->second.value; mPurgeableSize.count++; } // do we store this element in the wrapped list or the skia managed list auto& results = mIsCurrentValueWrapped ? mWrappedResults : mResults; // insert a copy of the element and all of its keys. We must make a copy here instead of // std::move() as we will continue to use these values later in the function and again // when we move on to process the next element. results.insert({mCurrentElement, mCurrentValues}); // insert the item into its mapped category auto result = results.find(resourceName); if (result != results.end()) { auto& resourceValues = result->second; auto totalResult = resourceValues.find(sizeResult->first); if (totalResult != resourceValues.end()) { ALOGE_IF(sizeResult->second.units != totalResult->second.units, "resource units do not match so the sum of resource type (%s) will be invalid", resourceName); totalResult->second.value += sizeResult->second.value; totalResult->second.count++; } else { ALOGE("an entry (%s) should not exist in the results without a size", resourceName); } } else { // only store the size for the top level resource results.insert({resourceName, {{sizeResult->first, sizeResult->second}}}); } resetCurrentElement(); } void SkiaMemoryReporter::dumpNumericValue(const char* dumpName, const char* valueName, const char* units, uint64_t value) { if (mCurrentElement != dumpName) { processCurrentElement(); mCurrentElement = dumpName; } mCurrentValues.insert({valueName, {units, value}}); } void SkiaMemoryReporter::dumpWrappedState(const char* dumpName, bool isWrappedObject) { if (mCurrentElement != dumpName) { processCurrentElement(); mCurrentElement = dumpName; } mIsCurrentValueWrapped = isWrappedObject; } void SkiaMemoryReporter::logOutput(std::string& log, bool wrappedResources) { // process the current element before logging processCurrentElement(); const auto& resultsMap = wrappedResources ? mWrappedResults : mResults; // log each individual element based on the resource map for (const auto& resourceCategory : mResourceMap) { // find the named item and print the totals const auto categoryItem = resultsMap.find(resourceCategory.second); if (categoryItem != resultsMap.end()) { auto result = categoryItem->second.find("size"); if (result != categoryItem->second.end()) { TraceValue traceValue = convertUnits(result->second); const char* entry = (traceValue.count > 1) ? "entries" : "entry"; StringAppendF(&log, " %s: %.2f %s (%d %s)\n", categoryItem->first.c_str(), traceValue.value, traceValue.units.c_str(), traceValue.count, entry); } if (mItemize) { for (const auto& individualItem : resultsMap) { // if the individual item matches the category then print all its details or // in the case of wrapped resources just print the wrapped size const char* categoryMatch = mapName(individualItem.first.c_str()); if (categoryMatch && strcmp(categoryMatch, resourceCategory.second) == 0) { auto result = individualItem.second.find("size"); TraceValue size = convertUnits(result->second); StringAppendF(&log, " %s: size[%.2f %s]", individualItem.first.c_str(), size.value, size.units.c_str()); if (!wrappedResources) { for (const auto& itemValues : individualItem.second) { if (strcmp("size", itemValues.first) == 0) { continue; } TraceValue traceValue = convertUnits(itemValues.second); if (traceValue.value == 0.0f) { StringAppendF(&log, " %s[%s]", itemValues.first, traceValue.units.c_str()); } else { StringAppendF(&log, " %s[%.2f %s]", itemValues.first, traceValue.value, traceValue.units.c_str()); } } } StringAppendF(&log, "\n"); } } } } } } void SkiaMemoryReporter::logTotals(std::string& log) { // process the current element before logging processCurrentElement(); TraceValue total = convertUnits(mTotalSize); TraceValue purgeable = convertUnits(mPurgeableSize); StringAppendF(&log, " %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value, total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str()); } SkiaMemoryReporter::TraceValue SkiaMemoryReporter::convertUnits(const TraceValue& value) { TraceValue output(value); if (SkString("bytes") == output.units && output.value >= 1024) { output.value = output.value / 1024.0f; output.units = "KB"; } if (SkString("KB") == output.units && output.value >= 1024) { output.value = output.value / 1024.0f; output.units = "MB"; } return output; } } /* namespace skia */ } /* namespace renderengine */ } /* namespace android */