1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "ATraceMemoryDump.h"
18
19 #include <include/gpu/ganesh/GrDirectContext.h>
20 #include <utils/Trace.h>
21
22 #include <cstring>
23
24 namespace android {
25 namespace uirenderer {
26 namespace skiapipeline {
27
28 // When purgeable is INVALID_MEMORY_SIZE it won't be logged at all.
29 #define INVALID_MEMORY_SIZE -1
30
31 /**
32 * Skia invokes the following SkTraceMemoryDump functions:
33 * 1. dumpNumericValue (dumpName, units="bytes", valueName="size")
34 * 2. dumpStringValue (dumpName, valueName="type") [optional -> for example CPU memory does not
35 * invoke dumpStringValue]
36 * 3. dumpNumericValue (dumpName, units="bytes", valueName="purgeable_size") [optional]
37 * 4. setMemoryBacking(dumpName, backingType) [optional -> for example Vulkan GPU resources do not
38 * invoke setMemoryBacking]
39 *
40 * ATraceMemoryDump calculates memory category first by looking at the "type" string passed to
41 * dumpStringValue and then by looking at "backingType" passed to setMemoryBacking.
42 * Only GPU Texture memory is tracked separately and everything else is grouped as one
43 * "Misc Memory" category.
44 */
45 static std::unordered_map<const char*, const char*> sResourceMap = {
46 {"malloc", "HWUI CPU Memory"}, // taken from setMemoryBacking(backingType)
47 {"gl_texture", "HWUI Texture Memory"}, // taken from setMemoryBacking(backingType)
48 {"Texture", "HWUI Texture Memory"}, // taken from dumpStringValue(value, valueName="type")
49 // Uncomment categories below to split "Misc Memory" into more brackets for debugging.
50 /*{"vk_buffer", "vk_buffer"},
51 {"gl_renderbuffer", "gl_renderbuffer"},
52 {"gl_buffer", "gl_buffer"},
53 {"RenderTarget", "RenderTarget"},
54 {"Stencil", "Stencil"},
55 {"Path Data", "Path Data"},
56 {"Buffer Object", "Buffer Object"},
57 {"Surface", "Surface"},*/
58 };
59
ATraceMemoryDump()60 ATraceMemoryDump::ATraceMemoryDump() {
61 mLastDumpName.reserve(100);
62 mCategory.reserve(100);
63 }
64
dumpNumericValue(const char * dumpName,const char * valueName,const char * units,uint64_t value)65 void ATraceMemoryDump::dumpNumericValue(const char* dumpName, const char* valueName,
66 const char* units, uint64_t value) {
67 if (!strcmp(units, "bytes")) {
68 recordAndResetCountersIfNeeded(dumpName);
69 if (!strcmp(valueName, "size")) {
70 mLastDumpValue = value;
71 } else if (!strcmp(valueName, "purgeable_size")) {
72 mLastPurgeableDumpValue = value;
73 }
74 }
75 }
76
dumpStringValue(const char * dumpName,const char * valueName,const char * value)77 void ATraceMemoryDump::dumpStringValue(const char* dumpName, const char* valueName,
78 const char* value) {
79 if (!strcmp(valueName, "type")) {
80 recordAndResetCountersIfNeeded(dumpName);
81 auto categoryIt = sResourceMap.find(value);
82 if (categoryIt != sResourceMap.end()) {
83 mCategory = categoryIt->second;
84 }
85 }
86 }
87
setMemoryBacking(const char * dumpName,const char * backingType,const char * backingObjectId)88 void ATraceMemoryDump::setMemoryBacking(const char* dumpName, const char* backingType,
89 const char* backingObjectId) {
90 recordAndResetCountersIfNeeded(dumpName);
91 auto categoryIt = sResourceMap.find(backingType);
92 if (categoryIt != sResourceMap.end()) {
93 mCategory = categoryIt->second;
94 }
95 }
96
97 /**
98 * startFrame is invoked before dumping anything. It resets counters from the previous frame.
99 * This is important, because if there is no new data for a given category trace would assume
100 * usage has not changed (instead of reporting 0).
101 */
startFrame()102 void ATraceMemoryDump::startFrame() {
103 resetCurrentCounter("");
104 for (auto& it : mCurrentValues) {
105 // Once a category is observed in at least one frame, it is always reported in subsequent
106 // frames (even if it is 0). Not logging a category to ATRACE would mean its value has not
107 // changed since the previous frame, which is not what we want.
108 it.second.memory = 0;
109 // If purgeableMemory is INVALID_MEMORY_SIZE, then logTraces won't log it at all.
110 if (it.second.purgeableMemory != INVALID_MEMORY_SIZE) {
111 it.second.purgeableMemory = 0;
112 }
113 }
114 }
115
116 /**
117 * logTraces reads from mCurrentValues and logs the counters with ATRACE.
118 *
119 * gpuMemoryIsAlreadyInDump must be true if GrDirectContext::dumpMemoryStatistics(...) was called
120 * with this tracer, false otherwise. Leaving this false allows this function to quickly query total
121 * and purgable GPU memory without the caller having to spend time in
122 * GrDirectContext::dumpMemoryStatistics(...) first, which iterates over every resource in the GPU
123 * cache. This can save significant time, but buckets all GPU memory into a single "misc" track,
124 * which may be a loss of granularity depending on the GPU backend and the categories defined in
125 * sResourceMap.
126 */
logTraces(bool gpuMemoryIsAlreadyInDump,GrDirectContext * grContext)127 void ATraceMemoryDump::logTraces(bool gpuMemoryIsAlreadyInDump, GrDirectContext* grContext) {
128 // Accumulate data from last dumpName
129 recordAndResetCountersIfNeeded("");
130 uint64_t hwui_all_frame_memory = 0;
131 for (auto& it : mCurrentValues) {
132 hwui_all_frame_memory += it.second.memory;
133 ATRACE_INT64(it.first.c_str(), it.second.memory);
134 if (it.second.purgeableMemory != INVALID_MEMORY_SIZE) {
135 ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableMemory);
136 }
137 }
138
139 if (!gpuMemoryIsAlreadyInDump && grContext) {
140 // Total GPU memory
141 int gpuResourceCount;
142 size_t gpuResourceBytes;
143 grContext->getResourceCacheUsage(&gpuResourceCount, &gpuResourceBytes);
144 hwui_all_frame_memory += (uint64_t)gpuResourceBytes;
145 ATRACE_INT64("HWUI Misc Memory", gpuResourceBytes);
146
147 // Purgable subset of GPU memory
148 size_t purgeableGpuResourceBytes = grContext->getResourceCachePurgeableBytes();
149 ATRACE_INT64("Purgeable HWUI Misc Memory", purgeableGpuResourceBytes);
150 }
151
152 ATRACE_INT64("HWUI All Memory", hwui_all_frame_memory);
153 }
154
155 /**
156 * recordAndResetCountersIfNeeded reads memory usage from mLastDumpValue/mLastPurgeableDumpValue and
157 * accumulates in mCurrentValues[category]. It makes provision to create a new category and track
158 * purgeable memory only if there is at least one observation.
159 * recordAndResetCountersIfNeeded won't do anything until all the information for a given dumpName
160 * is received.
161 */
recordAndResetCountersIfNeeded(const char * dumpName)162 void ATraceMemoryDump::recordAndResetCountersIfNeeded(const char* dumpName) {
163 if (!mLastDumpName.compare(dumpName)) {
164 // Still waiting for more data for current dumpName.
165 return;
166 }
167
168 // First invocation will have an empty mLastDumpName.
169 if (!mLastDumpName.empty()) {
170 // A new dumpName observed -> store the data already collected.
171 auto memoryCounter = mCurrentValues.find(mCategory);
172 if (memoryCounter != mCurrentValues.end()) {
173 memoryCounter->second.memory += mLastDumpValue;
174 if (mLastPurgeableDumpValue != INVALID_MEMORY_SIZE) {
175 if (memoryCounter->second.purgeableMemory == INVALID_MEMORY_SIZE) {
176 memoryCounter->second.purgeableMemory = mLastPurgeableDumpValue;
177 } else {
178 memoryCounter->second.purgeableMemory += mLastPurgeableDumpValue;
179 }
180 }
181 } else {
182 mCurrentValues[mCategory] = {mLastDumpValue, mLastPurgeableDumpValue};
183 }
184 }
185
186 // Reset counters and default category for the newly observed "dumpName".
187 resetCurrentCounter(dumpName);
188 }
189
resetCurrentCounter(const char * dumpName)190 void ATraceMemoryDump::resetCurrentCounter(const char* dumpName) {
191 mLastDumpValue = 0;
192 mLastPurgeableDumpValue = INVALID_MEMORY_SIZE;
193 mLastDumpName = dumpName;
194 // Categories not listed in sResourceMap are reported as "Misc Memory"
195 mCategory = "HWUI Misc Memory";
196 }
197
198 } /* namespace skiapipeline */
199 } /* namespace uirenderer */
200 } /* namespace android */
201