Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0e6d356a authored by Derek Sollenberger's avatar Derek Sollenberger
Browse files

Add additional logging to RenderEngine regarding Skia's cache contents

Bug: 182142615
Test: dumpsys SurfaceFlinger
Change-Id: I1b968e5877049d713a456ce824a8d41389fda891
parent b7f378a1
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -91,6 +91,7 @@ filegroup {
        "skia/debug/CaptureTimer.cpp",
        "skia/debug/CommonPool.cpp",
        "skia/debug/SkiaCapture.cpp",
        "skia/debug/SkiaMemoryReporter.cpp",
        "skia/filters/BlurFilter.cpp",
        "skia/filters/LinearEffect.cpp",
    ],
+43 −3
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@
#include <SkColorFilter.h>
#include <SkColorMatrix.h>
#include <SkColorSpace.h>
#include <SkGraphics.h>
#include <SkImage.h>
#include <SkImageFilters.h>
#include <SkRegion.h>
@@ -40,13 +41,13 @@
#include <ui/DebugUtils.h>
#include <ui/GraphicBuffer.h>
#include <utils/Trace.h>
#include "Cache.h"

#include <cmath>
#include <cstdint>
#include <memory>

#include "../gl/GLExtensions.h"
#include "Cache.h"
#include "ColorSpaces.h"
#include "SkBlendMode.h"
#include "SkImageInfo.h"
@@ -54,6 +55,7 @@
#include "filters/LinearEffect.h"
#include "log/log_main.h"
#include "skia/debug/SkiaCapture.h"
#include "skia/debug/SkiaMemoryReporter.h"
#include "system/graphics-base-v1.0.h"

namespace {
@@ -1249,9 +1251,38 @@ void SkiaGLRenderEngine::dump(std::string& result) {
    StringAppendF(&result, "RenderEngine shaders cached since last dump/primeCache: %d\n",
                  mSkSLCacheMonitor.shadersCachedSinceLastCall());

    std::vector<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"},
            {"skia", "Other"},
    };
    SkiaMemoryReporter cpuReporter(cpuResourceMap, false);
    SkGraphics::DumpMemoryStatistics(&cpuReporter);
    StringAppendF(&result, "Skia CPU Caches: ");
    cpuReporter.logTotals(result);
    cpuReporter.logOutput(result);

    {
        std::lock_guard<std::mutex> lock(mRenderingMutex);
        StringAppendF(&result, "RenderEngine texture cache size: %zu\n", mTextureCache.size());

        std::vector<ResourcePair> gpuResourceMap = {
                {"texture_renderbuffer", "Texture/RenderBuffer"},
                {"texture", "Texture"},
                {"gr_text_blob_cache", "Text"},
                {"skia", "Other"},
        };
        SkiaMemoryReporter gpuReporter(gpuResourceMap, true);
        mGrContext->dumpMemoryStatistics(&gpuReporter);
        StringAppendF(&result, "Skia's GPU Caches: ");
        gpuReporter.logTotals(result);
        gpuReporter.logOutput(result);
        StringAppendF(&result, "Skia's Wrapped Objects:\n");
        gpuReporter.logOutput(result, true);

        StringAppendF(&result, "RenderEngine AHB/BackendTexture cache size: %zu\n",
                      mTextureCache.size());
        StringAppendF(&result, "Dumping buffer ids...\n");
        // TODO(178539829): It would be nice to know which layer these are coming from and what
        // the texture sizes are.
@@ -1259,7 +1290,16 @@ void SkiaGLRenderEngine::dump(std::string& result) {
            StringAppendF(&result, "- 0x%" PRIx64 "\n", id);
        }
        StringAppendF(&result, "\n");
        StringAppendF(&result, "RenderEngine protected texture cache size: %zu\n",

        SkiaMemoryReporter gpuProtectedReporter(gpuResourceMap, true);
        mProtectedGrContext->dumpMemoryStatistics(&gpuProtectedReporter);
        StringAppendF(&result, "Skia's GPU Protected Caches: ");
        gpuProtectedReporter.logTotals(result);
        gpuProtectedReporter.logOutput(result);
        StringAppendF(&result, "Skia's Protected Wrapped Objects:\n");
        gpuProtectedReporter.logOutput(result, true);

        StringAppendF(&result, "RenderEngine protected AHB/BackendTexture cache size: %zu\n",
                      mProtectedTextureCache.size());
        StringAppendF(&result, "Dumping buffer ids...\n");
        for (const auto& [id, unused] : mProtectedTextureCache) {
+205 −0
Original line number Diff line number Diff line
/*
 * 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 <SkString.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, 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);
                        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);
                                } else {
                                    StringAppendF(&log, " %s[%.2f %s]", itemValues.first,
                                                  traceValue.value, traceValue.units);
                                }
                            }
                        }
                        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, purgeable.value, purgeable.units);
}

SkiaMemoryReporter::TraceValue SkiaMemoryReporter::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 skia */
} /* namespace renderengine */
} /* namespace android */
+107 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#pragma once

#include <SkTraceMemoryDump.h>

#include <string>
#include <unordered_map>
#include <vector>

namespace android {
namespace renderengine {
namespace skia {

// Mapping of resource substrings (1st element) that if found within a trace "dumpName"
// should be mapped to the category name (2nd element). All char* used in a resourcePair
// are expected to have a lifetime longer than the SkiaMemoryReporter in which they are used.
typedef std::pair<const char*, const char*> ResourcePair;

/*
 * Utility class for logging the CPU/GPU usage of Skia caches in a format that is specific
 * to RenderEngine.  HWUI has a similar logging class, but the data collected and the way
 * it is formatted and reported on are intended to be unique to each use case.
 */
class SkiaMemoryReporter : public SkTraceMemoryDump {
public:
    /**
     * Creates the reporter class that can be populated by various Skia entry points, like
     * SkGraphics and GrContext, as well as format and log the results.
     * @param resourceMap An array of values that maps a Skia dumpName into a user defined category.
     *                    The first vector entry that matches the dumpName is used for the mapping.
     * @param itemize if true when logging the categories the individual elements will be printed
     *                directly after the category details are printed.  Otherwise, only the category
     *                totals will be printed.
     */
    SkiaMemoryReporter(const std::vector<ResourcePair>& resourceMap, bool itemize);
    ~SkiaMemoryReporter() override {}

    void logOutput(std::string& log, bool wrappedResources = false);
    void logTotals(std::string& 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);
    }
    void dumpWrappedState(const char* dumpName, bool isWrappedObject) override;

    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 processCurrentElement();
    void resetCurrentElement();
    TraceValue convertUnits(const TraceValue& value);

    const std::vector<ResourcePair>& mResourceMap;
    const bool mItemize;

    // variables storing the size of all non-wrapped 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;
    bool mIsCurrentValueWrapped = false;

    // 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;
    std::unordered_map<std::string, std::unordered_map<const char*, TraceValue>> mWrappedResults;
};

} /* namespace skia */
} /* namespace renderengine */
} /* namespace android */
 No newline at end of file