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