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