1 /*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "rs_skia_memory_tracer.h"
17 #include <numeric>
18 namespace OHOS::Rosen {
19 constexpr uint32_t MEMUNIT_RATE = 1024;
20 static const std::unordered_map<RSTagTracker::SOURCETYPE, std::string> sourceToStringMap = {
21 {RSTagTracker::SOURCE_OTHER, "source_other"},
22 {RSTagTracker::SOURCE_RSCUSTOMMODIFIERDRAWABLE, "source_rscustommodifierdrawable"},
23 {RSTagTracker::SOURCE_RSBEGINBLENDERDRAWABLE, "source_rsbeginblenderdrawable"},
24 {RSTagTracker::SOURCE_RSSHADOWDRAWABLE, "source_rsshadowdrawable"},
25 {RSTagTracker::SOURCE_RSBACKGROUNDIMAGEDRAWABLE, "source_rsbackgroundimagedrawable"},
26 {RSTagTracker::SOURCE_RSBACKGROUNDEFFECTDRAWABLE, "source_rsbackgroundeffectdrawable"},
27 {RSTagTracker::SOURCE_RSUSEEFFECTDRAWABLE, "source_rsuseeffectdrawable"},
28 {RSTagTracker::SOURCE_RSDYNAMICLIGHTUPDRAWABLE, "source_rsdynamiclightupdrawable"},
29 {RSTagTracker::SOURCE_RSBINARIZATIONDRAWABLE, "source_rsbinarizationdrawable"},
30 {RSTagTracker::SOURCE_RSCOLORFILTERDRAWABLE, "source_rscolorfilterdrawable"},
31 {RSTagTracker::SOURCE_RSLIGHTUPEFFECTDRAWABLE, "source_rslightupeffectdrawable"},
32 {RSTagTracker::SOURCE_RSDYNAMICDIMDRAWABLE, "source_rsdynamicdimdrawable"},
33 {RSTagTracker::SOURCE_RSFOREGROUNDFILTERDRAWABLE, "source_rsforegroundfilterdrawable"},
34 {RSTagTracker::SOURCE_RSFOREGROUNDFILTERRESTOREDRAWABLE, "source_rsforegroundfilterrestoredrawable"},
35 {RSTagTracker::SOURCE_RSPIXELSTRETCHDRAWABLE, "source_rspixelstretchdrawable"},
36 {RSTagTracker::SOURCE_RSPOINTLIGHTDRAWABLE, "source_rspointlightdrawable"},
37 {RSTagTracker::SOURCE_RSPROPERTYDRAWABLE, "source_rspropertydrawable"},
38 {RSTagTracker::SOURCE_RSFILTERDRAWABLE, "source_rsfilterdrawable"},
39 {RSTagTracker::SOURCE_FINISHOFFSCREENRENDER, "source_finishoffscreenrender"},
40 {RSTagTracker::SOURCE_DRAWSELFDRAWINGNODEBUFFER, "source_drawselfdrawingnodebuffer"},
41 {RSTagTracker::SOURCE_ONCAPTURE, "source_oncapture"},
42 {RSTagTracker::SOURCE_INITCACHEDSURFACE, "source_initcachedsurface"},
43 {RSTagTracker::SOURCE_DRAWRENDERCONTENT, "source_drawrendercontent"},
44 {RSTagTracker::SOURCE_FILTERCACHEENABLEVMA, "source_filtercacheenablevma"},
45 };
46
SkiaMemoryTracer(const std::vector<ResourcePair> & resourceMap,bool itemizeType)47 SkiaMemoryTracer::SkiaMemoryTracer(const std::vector<ResourcePair>& resourceMap, bool itemizeType)
48 : resourceMap_(resourceMap), itemizeType_(itemizeType), totalSize_("bytes", 0),
49 purgeableSize_("bytes", 0), externalTextureSize_("bytes", 0)
50 {}
51
SkiaMemoryTracer(const char * categoryKey,bool itemizeType)52 SkiaMemoryTracer::SkiaMemoryTracer(const char* categoryKey, bool itemizeType)
53 : categoryKey_(categoryKey), itemizeType_(itemizeType), totalSize_("bytes", 0),
54 purgeableSize_("bytes", 0), externalTextureSize_("bytes", 0)
55 {}
56
SourceType2String(RSTagTracker::SOURCETYPE type)57 std::string SkiaMemoryTracer::SourceType2String(RSTagTracker::SOURCETYPE type)
58 {
59 auto it = sourceToStringMap.find(type);
60 if (it != sourceToStringMap.end()) {
61 return it->second;
62 }
63 return "";
64 }
65
MapName(const char * resourceName)66 const char* SkiaMemoryTracer::MapName(const char* resourceName)
67 {
68 for (auto& resource : resourceMap_) {
69 if (SkStrContains(resourceName, resource.first)) {
70 return resource.second;
71 }
72 }
73 return nullptr;
74 }
75
ProcessElement()76 void SkiaMemoryTracer::ProcessElement()
77 {
78 if (!currentElement_.empty()) {
79 // Only count elements that contain "size", other values just provide metadata.
80 auto sizeResult = currentValues_.find("size");
81 if (sizeResult != currentValues_.end()) {
82 totalSize_.value += sizeResult->second.value;
83 totalSize_.count++;
84 } else {
85 currentElement_.clear();
86 currentValues_.clear();
87 return;
88 }
89
90 // find the purgeable size if one exists
91 auto purgeableResult = currentValues_.find("purgeable_size");
92 if (purgeableResult != currentValues_.end()) {
93 purgeableSize_.value += purgeableResult->second.value;
94 purgeableSize_.count++;
95 }
96
97 // find the type if one exists
98 std::string type;
99 auto typeResult = currentValues_.find("type");
100 if (typeResult != currentValues_.end()) {
101 type = typeResult->second.units.c_str();
102 } else if (itemizeType_) {
103 type = "Other";
104 } else {
105 type = "";
106 }
107
108 // compute the type if we are itemizing or use the default "size" if we are not
109 std::string key = (itemizeType_) ? type : sizeResult->first;
110
111 // compute the external texture size if one exists
112 std::string externalPrefix = "External";
113 if (key.size() >= externalPrefix.size() && key.compare(0, externalPrefix.size(), externalPrefix) == 0) {
114 externalTextureSize_.value += sizeResult->second.value;
115 externalTextureSize_.count++;
116 }
117
118 // compute the top level element name using either the map or category key
119 const char* resourceName = MapName(currentElement_.c_str());
120 if (categoryKey_ != nullptr) {
121 // find the category if one exists
122 auto categoryResult = currentValues_.find(categoryKey_);
123 if (categoryResult != currentValues_.end()) {
124 resourceName = categoryResult->second.units.c_str();
125 }
126 }
127
128 // if we don't have a resource name then we don't know how to label the
129 // data and should abort.
130 if (resourceName == nullptr) {
131 resourceName = currentElement_.c_str();
132 }
133
134 auto result = results_.find(resourceName);
135 if (result == results_.end()) {
136 std::string strResourceName = resourceName;
137 TraceValue sizeValue = sizeResult->second;
138 std::unordered_map<std::string, TraceValue> typeItem{{key, sizeValue}};
139 results_.insert({ strResourceName, typeItem });
140 } else {
141 auto& resourceValues = result->second;
142 typeResult = resourceValues.find(key);
143 if (typeResult == resourceValues.end()) {
144 resourceValues.insert({ key, sizeResult->second });
145 } else {
146 typeResult->second.value += sizeResult->second.value;
147 typeResult->second.count++;
148 }
149 }
150
151 // find the source if one exists
152 std::string sourceType;
153 auto sourceIdResult = currentValues_.find("source");
154 if (sourceIdResult != currentValues_.end()) {
155 sourceType = SourceType2String(
156 static_cast<RSTagTracker::SOURCETYPE>(static_cast<uint32_t>(sourceIdResult->second.value)));
157 } else if (itemizeType_) {
158 sourceType = "source_other";
159 } else {
160 sourceType = "";
161 }
162
163 std::string strResourceName = resourceName; // current tag
164 TraceValue sizeValue = sizeResult->second; // current size info
165 auto sourceResult = sourceTagResults_.find(sourceType);
166 if (sourceResult == sourceTagResults_.end()) { // 1. add new source item
167 std::unordered_map<std::string, TraceValue> typeItem{{key, sizeValue}};
168 std::unordered_map<std::string,
169 std::unordered_map<std::string, TraceValue>> tagItem{{strResourceName, typeItem}};
170 sourceTagResults_.insert({sourceType, tagItem});
171 } else { // 2.update source item
172 auto& sourceValues = sourceResult->second;
173 auto tagResult = sourceValues.find(strResourceName);
174 if (tagResult == sourceValues.end()) { // 2.1 add new tag item in current source
175 std::unordered_map<std::string, TraceValue> typeItem{{key, sizeValue}};
176 sourceResult->second.insert({strResourceName, typeItem});
177 } else { // 2.2 update tag item in current source
178 auto& typeValues = tagResult->second;
179 typeResult = typeValues.find(key);
180 if (typeResult == typeValues.end()) { // 2.2.1 add new type item in current tag
181 typeValues.insert({key, sizeValue});
182 } else { // 2.2.2 update type item in current tag
183 typeResult->second.value += sizeValue.value;
184 typeResult->second.count++;
185 }
186 }
187 }
188 }
189
190 currentElement_.clear();
191 currentValues_.clear();
192 }
193
dumpNumericValue(const char * dumpName,const char * valueName,const char * units,uint64_t value)194 void SkiaMemoryTracer::dumpNumericValue(const char* dumpName, const char* valueName, const char* units, uint64_t value)
195 {
196 if (currentElement_ != dumpName) {
197 ProcessElement();
198 currentElement_ = dumpName;
199 }
200 currentValues_.insert({ valueName, { units, value } });
201 }
202
LogOutput(DfxString & log)203 void SkiaMemoryTracer::LogOutput(DfxString& log)
204 {
205 // process any remaining elements
206 ProcessElement();
207
208 for (const auto& sourceItem: sourceTagResults_) {
209 if (itemizeType_) {
210 log.AppendFormat(" %s:\n", sourceItem.first.c_str()); // source_xx
211 for (const auto& tagItem: sourceItem.second) {
212 log.AppendFormat(" %s:\n", tagItem.first.c_str()); // skia/sk_glyph_cache
213 for (const auto& typeItem: tagItem.second) {
214 TraceValue traceValue = ConvertUnits(typeItem.second);
215 const char* entry = (traceValue.count > 1) ? "entries" : "entry";
216 log.AppendFormat(" %s: %.2f %s (%d %s)\n", typeItem.first.c_str(), traceValue.value,
217 traceValue.units.c_str(), traceValue.count, entry);
218 }
219 }
220 } else {
221 for (const auto& tagItem: sourceItem.second) {
222 auto result = tagItem.second.find("size"); // Logically not empty
223 TraceValue traceValue = ConvertUnits(result->second);
224 const char* entry = (traceValue.count > 1) ? "entries" : "entry";
225 log.AppendFormat(" %s: %.2f %s (%d %s)\n", tagItem.first.c_str(), traceValue.value,
226 traceValue.units.c_str(), traceValue.count, entry);
227 }
228 }
229 }
230 }
231
GetGLMemorySize()232 float SkiaMemoryTracer::GetGLMemorySize()
233 {
234 ProcessElement();
235 // exclude scratch memory
236 // cause scratch memory is generated by animation and effect. which is not response by app
237 size_t totalGpuSizeApp = 0;
238 for (const auto& namedItem : results_) {
239 if (namedItem.first == "Scratch" || namedItem.first == "skia/gr_text_blob_cache") {
240 continue;
241 }
242 totalGpuSizeApp = std::accumulate(namedItem.second.begin(), namedItem.second.end(), totalGpuSizeApp,
243 [](int total, const auto& typedValue) { return total + typedValue.second.value; });
244 }
245 return totalGpuSizeApp;
246 }
247
GetGpuMemorySizeInMB()248 float SkiaMemoryTracer::GetGpuMemorySizeInMB()
249 {
250 ProcessElement();
251 return ConvertToMB(totalSize_);
252 }
253
LogTotals(DfxString & log)254 void SkiaMemoryTracer::LogTotals(DfxString& log)
255 {
256 TraceValue total = ConvertUnits(totalSize_);
257 TraceValue purgeable = ConvertUnits(purgeableSize_);
258 TraceValue externalTexture = ConvertUnits(externalTextureSize_);
259 log.AppendFormat(" %.0f bytes, %.2f %s (%.2f %s is purgeable, %.2f %s is external)\n",
260 totalSize_.value, total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str(),
261 externalTexture.value, externalTexture.units.c_str());
262 }
263
ConvertUnits(const TraceValue & value)264 SkiaMemoryTracer::TraceValue SkiaMemoryTracer::ConvertUnits(const TraceValue& value)
265 {
266 TraceValue output(value);
267 if (output.units == SkString("bytes") && output.value >= MEMUNIT_RATE) {
268 output.value = output.value / MEMUNIT_RATE;
269 output.units = "KB";
270 }
271 if (output.units == SkString("KB") && output.value >= MEMUNIT_RATE) {
272 output.value = output.value / MEMUNIT_RATE;
273 output.units = "MB";
274 }
275 return output;
276 }
277
ConvertToMB(const TraceValue & value)278 float SkiaMemoryTracer::ConvertToMB(const TraceValue& value)
279 {
280 if (value.units == SkString("bytes")) {
281 return value.value / MEMUNIT_RATE / MEMUNIT_RATE;
282 }
283 if (value.units == SkString("KB")) {
284 return value.value / MEMUNIT_RATE;
285 }
286 return value.value;
287 }
288
289 } // namespace OHOS::Rosen