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