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