Merge "Add memory tracing in HWUI"

This commit is contained in:
TreeHugger Robot
2020-01-10 19:45:22 +00:00
committed by Android (Google) Code Review
12 changed files with 271 additions and 276 deletions

View File

@@ -215,6 +215,7 @@ cc_defaults {
android: {
srcs: [
"pipeline/skia/ATraceMemoryDump.cpp",
"pipeline/skia/GLFunctorDrawable.cpp",
"pipeline/skia/LayerDrawable.cpp",
"pipeline/skia/ShaderCache.cpp",
@@ -244,7 +245,6 @@ cc_defaults {
"DeviceInfo.cpp",
"FrameInfo.cpp",
"FrameInfoVisualizer.cpp",
"GpuMemoryTracker.cpp",
"HardwareBitmapUploader.cpp",
"HWUIProperties.sysprop",
"JankTracker.cpp",
@@ -325,7 +325,6 @@ cc_test {
"tests/unit/DamageAccumulatorTests.cpp",
"tests/unit/DeferredLayerUpdaterTests.cpp",
"tests/unit/FatVectorTests.cpp",
"tests/unit/GpuMemoryTrackerTests.cpp",
"tests/unit/GraphicsStatsServiceTests.cpp",
"tests/unit/LayerUpdateQueueTests.cpp",
"tests/unit/LinearAllocatorTests.cpp",

View File

@@ -1,122 +0,0 @@
/*
* Copyright (C) 2016 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 "utils/StringUtils.h"
#include <GpuMemoryTracker.h>
#include <cutils/compiler.h>
#include <utils/Trace.h>
#include <array>
#include <sstream>
#include <unordered_set>
#include <vector>
namespace android {
namespace uirenderer {
pthread_t gGpuThread = 0;
#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount)
const char* TYPE_NAMES[] = {
"Texture", "OffscreenBuffer", "Layer",
};
struct TypeStats {
int totalSize = 0;
int count = 0;
};
static std::array<TypeStats, NUM_TYPES> gObjectStats;
static std::unordered_set<GpuMemoryTracker*> gObjectSet;
void GpuMemoryTracker::notifySizeChanged(int newSize) {
int delta = newSize - mSize;
mSize = newSize;
gObjectStats[static_cast<int>(mType)].totalSize += delta;
}
void GpuMemoryTracker::startTrackingObject() {
auto result = gObjectSet.insert(this);
LOG_ALWAYS_FATAL_IF(!result.second,
"startTrackingObject() on %p failed, already being tracked!", this);
gObjectStats[static_cast<int>(mType)].count++;
}
void GpuMemoryTracker::stopTrackingObject() {
size_t removed = gObjectSet.erase(this);
LOG_ALWAYS_FATAL_IF(removed != 1, "stopTrackingObject removed %zd, is %p not being tracked?",
removed, this);
gObjectStats[static_cast<int>(mType)].count--;
}
void GpuMemoryTracker::onGpuContextCreated() {
LOG_ALWAYS_FATAL_IF(gGpuThread != 0,
"We already have a gpu thread? "
"current = %lu, gpu thread = %lu",
pthread_self(), gGpuThread);
gGpuThread = pthread_self();
}
void GpuMemoryTracker::onGpuContextDestroyed() {
gGpuThread = 0;
if (CC_UNLIKELY(gObjectSet.size() > 0)) {
std::stringstream os;
dump(os);
ALOGE("%s", os.str().c_str());
LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size());
}
}
void GpuMemoryTracker::dump() {
std::stringstream strout;
dump(strout);
ALOGD("%s", strout.str().c_str());
}
void GpuMemoryTracker::dump(std::ostream& stream) {
for (int type = 0; type < NUM_TYPES; type++) {
const TypeStats& stats = gObjectStats[type];
stream << TYPE_NAMES[type];
stream << " is using " << SizePrinter{stats.totalSize};
stream << ", count = " << stats.count;
stream << std::endl;
}
}
int GpuMemoryTracker::getInstanceCount(GpuObjectType type) {
return gObjectStats[static_cast<int>(type)].count;
}
int GpuMemoryTracker::getTotalSize(GpuObjectType type) {
return gObjectStats[static_cast<int>(type)].totalSize;
}
void GpuMemoryTracker::onFrameCompleted() {
if (ATRACE_ENABLED()) {
char buf[128];
for (int type = 0; type < NUM_TYPES; type++) {
snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]);
const TypeStats& stats = gObjectStats[type];
ATRACE_INT(buf, stats.totalSize);
snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]);
ATRACE_INT(buf, stats.count);
}
}
}
} // namespace uirenderer
} // namespace android;

View File

@@ -1,77 +0,0 @@
/*
* Copyright (C) 2016 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 <pthread.h>
#include <ostream>
#include <log/log.h>
namespace android {
namespace uirenderer {
extern pthread_t gGpuThread;
#define ASSERT_GPU_THREAD() \
LOG_ALWAYS_FATAL_IF(!pthread_equal(gGpuThread, pthread_self()), \
"Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \
"!= gpu thread %lu", \
this, static_cast<int>(mType), mSize, pthread_self(), gGpuThread)
enum class GpuObjectType {
Texture = 0,
OffscreenBuffer,
Layer,
TypeCount,
};
class GpuMemoryTracker {
public:
GpuObjectType objectType() { return mType; }
int objectSize() { return mSize; }
static void onGpuContextCreated();
static void onGpuContextDestroyed();
static void dump();
static void dump(std::ostream& stream);
static int getInstanceCount(GpuObjectType type);
static int getTotalSize(GpuObjectType type);
static void onFrameCompleted();
protected:
explicit GpuMemoryTracker(GpuObjectType type) : mType(type) {
ASSERT_GPU_THREAD();
startTrackingObject();
}
~GpuMemoryTracker() {
notifySizeChanged(0);
stopTrackingObject();
}
void notifySizeChanged(int newSize);
private:
void startTrackingObject();
void stopTrackingObject();
int mSize = 0;
GpuObjectType mType;
};
} // namespace uirenderer
} // namespace android;

View File

@@ -0,0 +1,175 @@
/*
* 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>
namespace android {
namespace uirenderer {
namespace skiapipeline {
// When purgeable is INVALID_TIME it won't be logged at all.
#define INVALID_TIME -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
* "GPU Memory" category.
*/
static std::unordered_map<const char*, const char*> sResourceMap = {
{"malloc", "Graphics CPU Memory"}, // taken from setMemoryBacking(backingType)
{"gl_texture", "Graphics Texture Memory"}, // taken from setMemoryBacking(backingType)
{"Texture",
"Graphics Texture Memory"}, // taken from dumpStringValue(value, valueName="type")
// Uncomment categories below to split "GPU 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.time = 0;
// If purgeableTime is INVALID_TIME, then logTraces won't log it at all.
if (it.second.purgeableTime != INVALID_TIME) {
it.second.purgeableTime = 0;
}
}
}
/**
* logTraces reads from mCurrentValues and logs the counters with ATRACE.
*/
void ATraceMemoryDump::logTraces() {
// Accumulate data from last dumpName
recordAndResetCountersIfNeeded("");
for (auto& it : mCurrentValues) {
ATRACE_INT64(it.first.c_str(), it.second.time);
if (it.second.purgeableTime != INVALID_TIME) {
ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableTime);
}
}
}
/**
* 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.time += mLastDumpValue;
if (mLastPurgeableDumpValue != INVALID_TIME) {
if (memoryCounter->second.purgeableTime == INVALID_TIME) {
memoryCounter->second.purgeableTime = mLastPurgeableDumpValue;
} else {
memoryCounter->second.purgeableTime += 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_TIME;
mLastDumpName = dumpName;
// Categories not listed in sResourceMap are reported as "GPU memory"
mCategory = "GPU Memory";
}
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -0,0 +1,79 @@
/*
* 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.
*/
#pragma once
#include <SkString.h>
#include <SkTraceMemoryDump.h>
#include <string>
#include <unordered_map>
#include <utility>
namespace android {
namespace uirenderer {
namespace skiapipeline {
class ATraceMemoryDump : public SkTraceMemoryDump {
public:
ATraceMemoryDump();
~ATraceMemoryDump() override {}
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;
LevelOfDetail getRequestedDetails() const override {
return SkTraceMemoryDump::kLight_LevelOfDetail;
}
bool shouldDumpWrappedObjects() const override { return false; }
void setMemoryBacking(const char* dumpName, const char* backingType,
const char* backingObjectId) override;
void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override {}
void startFrame();
void logTraces();
private:
std::string mLastDumpName;
uint64_t mLastDumpValue;
uint64_t mLastPurgeableDumpValue;
std::string mCategory;
struct TraceValue {
uint64_t time;
uint64_t purgeableTime;
};
// keys are define in sResourceMap
std::unordered_map<std::string, TraceValue> mCurrentValues;
void recordAndResetCountersIfNeeded(const char* dumpName);
void resetCurrentCounter(const char* dumpName);
};
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -16,7 +16,6 @@
#include "renderstate/RenderState.h"
#include "renderthread/RenderThread.h"
#include "GpuMemoryTracker.h"
namespace android {
namespace uirenderer {
@@ -25,15 +24,10 @@ RenderState::RenderState(renderthread::RenderThread& thread) : mRenderThread(thr
mThreadId = pthread_self();
}
void RenderState::onContextCreated() {
GpuMemoryTracker::onGpuContextCreated();
}
void RenderState::onContextDestroyed() {
for(auto callback : mContextCallbacks) {
callback->onContextDestroyed();
}
GpuMemoryTracker::onGpuContextDestroyed();
}
void RenderState::postDecStrong(VirtualLightRefBase* object) {

View File

@@ -62,7 +62,6 @@ private:
~RenderState() {}
// Context notifications are only to be triggered by renderthread::RenderThread
void onContextCreated();
void onContextDestroyed();
std::set<IGpuContextCallback*> mContextCallbacks;

View File

@@ -20,10 +20,12 @@
#include "Layer.h"
#include "Properties.h"
#include "RenderThread.h"
#include "pipeline/skia/ATraceMemoryDump.h"
#include "pipeline/skia/ShaderCache.h"
#include "pipeline/skia/SkiaMemoryTracer.h"
#include "renderstate/RenderState.h"
#include "thread/CommonPool.h"
#include <utils/Trace.h>
#include <GrContextOptions.h>
#include <SkExecutor.h>
@@ -184,6 +186,18 @@ void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState)
gpuTracer.logTotals(log);
}
void CacheManager::onFrameCompleted() {
if (ATRACE_ENABLED()) {
static skiapipeline::ATraceMemoryDump tracer;
tracer.startFrame();
SkGraphics::DumpMemoryStatistics(&tracer);
if (mGrContext) {
mGrContext->dumpMemoryStatistics(&tracer);
}
tracer.logTraces();
}
}
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -50,6 +50,7 @@ public:
size_t getCacheSize() const { return mMaxResourceBytes; }
size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
void onFrameCompleted();
private:
friend class RenderThread;

View File

@@ -16,7 +16,6 @@
#include "CanvasContext.h"
#include <GpuMemoryTracker.h>
#include <apex/window.h>
#include <fcntl.h>
#include <strings.h>
@@ -558,7 +557,7 @@ void CanvasContext::draw() {
mJankTracker.finishGpuDraw(*forthBehind);
}
GpuMemoryTracker::onFrameCompleted();
mRenderThread.cacheManager().onFrameCompleted();
}
// Called by choreographer to do an RT-driven animation

View File

@@ -270,7 +270,6 @@ void RenderThread::setGrContext(sk_sp<GrContext> context) {
}
mGrContext = std::move(context);
if (mGrContext) {
mRenderState->onContextCreated();
DeviceInfo::setMaxTextureSize(mGrContext->maxRenderTargetSize());
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright (C) 2016 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 <GpuMemoryTracker.h>
#include <gtest/gtest.h>
#include "renderthread/EglManager.h"
#include "renderthread/RenderThread.h"
#include "tests/common/TestUtils.h"
#include <utils/StrongPointer.h>
using namespace android;
using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
class TestGPUObject : public GpuMemoryTracker {
public:
TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {}
void changeSize(int newSize) { notifySizeChanged(newSize); }
};
// Other tests may have created a renderthread and EGL context.
// This will destroy the EGLContext on RenderThread if it exists so that the
// current thread can spoof being a GPU thread
static void destroyEglContext() {
if (TestUtils::isRenderThreadRunning()) {
TestUtils::runOnRenderThread([](RenderThread& thread) { thread.destroyRenderingContext(); });
}
}
TEST(GpuMemoryTracker, sizeCheck) {
destroyEglContext();
GpuMemoryTracker::onGpuContextCreated();
ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
{
TestGPUObject myObj;
ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
myObj.changeSize(500);
ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
myObj.changeSize(1000);
ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
myObj.changeSize(300);
ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
}
ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
GpuMemoryTracker::onGpuContextDestroyed();
}