blob: 756b937f7de3c7ea3173ca3a59668b1dd2f9b502 [file] [log] [blame]
/*
* Copyright (C) 2020 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 "ATraceMemoryDump.h"
#include <utils/Trace.h>
#include <cstring>
#include "GrDirectContext.h"
namespace android {
namespace uirenderer {
namespace skiapipeline {
// When purgeable is INVALID_MEMORY_SIZE it won't be logged at all.
#define INVALID_MEMORY_SIZE -1
/**
* Skia invokes the following SkTraceMemoryDump functions:
* 1. dumpNumericValue (dumpName, units="bytes", valueName="size")
* 2. dumpStringValue (dumpName, valueName="type") [optional -> for example CPU memory does not
* invoke dumpStringValue]
* 3. dumpNumericValue (dumpName, units="bytes", valueName="purgeable_size") [optional]
* 4. setMemoryBacking(dumpName, backingType) [optional -> for example Vulkan GPU resources do not
* invoke setMemoryBacking]
*
* ATraceMemoryDump calculates memory category first by looking at the "type" string passed to
* dumpStringValue and then by looking at "backingType" passed to setMemoryBacking.
* Only GPU Texture memory is tracked separately and everything else is grouped as one
* "Misc Memory" category.
*/
static std::unordered_map<const char*, const char*> sResourceMap = {
{"malloc", "HWUI CPU Memory"}, // taken from setMemoryBacking(backingType)
{"gl_texture", "HWUI Texture Memory"}, // taken from setMemoryBacking(backingType)
{"Texture", "HWUI Texture Memory"}, // taken from dumpStringValue(value, valueName="type")
// Uncomment categories below to split "Misc Memory" into more brackets for debugging.
/*{"vk_buffer", "vk_buffer"},
{"gl_renderbuffer", "gl_renderbuffer"},
{"gl_buffer", "gl_buffer"},
{"RenderTarget", "RenderTarget"},
{"Stencil", "Stencil"},
{"Path Data", "Path Data"},
{"Buffer Object", "Buffer Object"},
{"Surface", "Surface"},*/
};
ATraceMemoryDump::ATraceMemoryDump() {
mLastDumpName.reserve(100);
mCategory.reserve(100);
}
void ATraceMemoryDump::dumpNumericValue(const char* dumpName, const char* valueName,
const char* units, uint64_t value) {
if (!strcmp(units, "bytes")) {
recordAndResetCountersIfNeeded(dumpName);
if (!strcmp(valueName, "size")) {
mLastDumpValue = value;
} else if (!strcmp(valueName, "purgeable_size")) {
mLastPurgeableDumpValue = value;
}
}
}
void ATraceMemoryDump::dumpStringValue(const char* dumpName, const char* valueName,
const char* value) {
if (!strcmp(valueName, "type")) {
recordAndResetCountersIfNeeded(dumpName);
auto categoryIt = sResourceMap.find(value);
if (categoryIt != sResourceMap.end()) {
mCategory = categoryIt->second;
}
}
}
void ATraceMemoryDump::setMemoryBacking(const char* dumpName, const char* backingType,
const char* backingObjectId) {
recordAndResetCountersIfNeeded(dumpName);
auto categoryIt = sResourceMap.find(backingType);
if (categoryIt != sResourceMap.end()) {
mCategory = categoryIt->second;
}
}
/**
* startFrame is invoked before dumping anything. It resets counters from the previous frame.
* This is important, because if there is no new data for a given category trace would assume
* usage has not changed (instead of reporting 0).
*/
void ATraceMemoryDump::startFrame() {
resetCurrentCounter("");
for (auto& it : mCurrentValues) {
// Once a category is observed in at least one frame, it is always reported in subsequent
// frames (even if it is 0). Not logging a category to ATRACE would mean its value has not
// changed since the previous frame, which is not what we want.
it.second.memory = 0;
// If purgeableMemory is INVALID_MEMORY_SIZE, then logTraces won't log it at all.
if (it.second.purgeableMemory != INVALID_MEMORY_SIZE) {
it.second.purgeableMemory = 0;
}
}
}
/**
* logTraces reads from mCurrentValues and logs the counters with ATRACE.
*
* gpuMemoryIsAlreadyInDump must be true if GrDirectContext::dumpMemoryStatistics(...) was called
* with this tracer, false otherwise. Leaving this false allows this function to quickly query total
* and purgable GPU memory without the caller having to spend time in
* GrDirectContext::dumpMemoryStatistics(...) first, which iterates over every resource in the GPU
* cache. This can save significant time, but buckets all GPU memory into a single "misc" track,
* which may be a loss of granularity depending on the GPU backend and the categories defined in
* sResourceMap.
*/
void ATraceMemoryDump::logTraces(bool gpuMemoryIsAlreadyInDump, GrDirectContext* grContext) {
// Accumulate data from last dumpName
recordAndResetCountersIfNeeded("");
uint64_t hwui_all_frame_memory = 0;
for (auto& it : mCurrentValues) {
hwui_all_frame_memory += it.second.memory;
ATRACE_INT64(it.first.c_str(), it.second.memory);
if (it.second.purgeableMemory != INVALID_MEMORY_SIZE) {
ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableMemory);
}
}
if (!gpuMemoryIsAlreadyInDump && grContext) {
// Total GPU memory
int gpuResourceCount;
size_t gpuResourceBytes;
grContext->getResourceCacheUsage(&gpuResourceCount, &gpuResourceBytes);
hwui_all_frame_memory += (uint64_t)gpuResourceBytes;
ATRACE_INT64("HWUI Misc Memory", gpuResourceBytes);
// Purgable subset of GPU memory
size_t purgeableGpuResourceBytes = grContext->getResourceCachePurgeableBytes();
ATRACE_INT64("Purgeable HWUI Misc Memory", purgeableGpuResourceBytes);
}
ATRACE_INT64("HWUI All Memory", hwui_all_frame_memory);
}
/**
* recordAndResetCountersIfNeeded reads memory usage from mLastDumpValue/mLastPurgeableDumpValue and
* accumulates in mCurrentValues[category]. It makes provision to create a new category and track
* purgeable memory only if there is at least one observation.
* recordAndResetCountersIfNeeded won't do anything until all the information for a given dumpName
* is received.
*/
void ATraceMemoryDump::recordAndResetCountersIfNeeded(const char* dumpName) {
if (!mLastDumpName.compare(dumpName)) {
// Still waiting for more data for current dumpName.
return;
}
// First invocation will have an empty mLastDumpName.
if (!mLastDumpName.empty()) {
// A new dumpName observed -> store the data already collected.
auto memoryCounter = mCurrentValues.find(mCategory);
if (memoryCounter != mCurrentValues.end()) {
memoryCounter->second.memory += mLastDumpValue;
if (mLastPurgeableDumpValue != INVALID_MEMORY_SIZE) {
if (memoryCounter->second.purgeableMemory == INVALID_MEMORY_SIZE) {
memoryCounter->second.purgeableMemory = mLastPurgeableDumpValue;
} else {
memoryCounter->second.purgeableMemory += mLastPurgeableDumpValue;
}
}
} else {
mCurrentValues[mCategory] = {mLastDumpValue, mLastPurgeableDumpValue};
}
}
// Reset counters and default category for the newly observed "dumpName".
resetCurrentCounter(dumpName);
}
void ATraceMemoryDump::resetCurrentCounter(const char* dumpName) {
mLastDumpValue = 0;
mLastPurgeableDumpValue = INVALID_MEMORY_SIZE;
mLastDumpName = dumpName;
// Categories not listed in sResourceMap are reported as "Misc Memory"
mCategory = "HWUI Misc Memory";
}
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */