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

Commit 8ec34162 authored by Derek Sollenberger's avatar Derek Sollenberger Committed by Android (Google) Code Review
Browse files

Merge "Print detailed memory usage of Skia for dumpsys gfxinfo" into pi-dev

parents 3cafea07 0057db22
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -168,6 +168,7 @@ cc_defaults {
        "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",
+175 −0
Original line number Diff line number Diff line
/*
 * 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 */
+88 −0
Original line number Diff line number Diff line
/*
 * 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
+26 −9
Original line number Diff line number Diff line
@@ -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 @@ void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState)
        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 @@ void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState)
                             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 */