1 /*
2 * Copyright (C) 2021 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 #undef LOG_TAG
17 #define LOG_TAG "RenderEngine"
18
19 #include "SkiaMemoryReporter.h"
20
21 #include <SkString.h>
22 #include <android-base/stringprintf.h>
23 #include <log/log_main.h>
24
25 namespace android {
26 namespace renderengine {
27 namespace skia {
28
29 using base::StringAppendF;
30
SkiaMemoryReporter(const std::vector<ResourcePair> & resourceMap,bool itemize)31 SkiaMemoryReporter::SkiaMemoryReporter(const std::vector<ResourcePair>& resourceMap, bool itemize)
32 : mResourceMap(resourceMap),
33 mItemize(itemize),
34 mTotalSize("bytes", 0),
35 mPurgeableSize("bytes", 0) {}
36
mapName(const char * resourceName)37 const char* SkiaMemoryReporter::mapName(const char* resourceName) {
38 for (auto& resource : mResourceMap) {
39 if (SkStrContains(resourceName, resource.first)) {
40 return resource.second;
41 }
42 }
43 return nullptr;
44 }
45
resetCurrentElement()46 void SkiaMemoryReporter::resetCurrentElement() {
47 mCurrentElement.clear();
48 mCurrentValues.clear();
49 mIsCurrentValueWrapped = false;
50 }
51
processCurrentElement()52 void SkiaMemoryReporter::processCurrentElement() {
53 // compute the top level element name using the map
54 const char* resourceName = mCurrentElement.empty() ? nullptr : mapName(mCurrentElement.c_str());
55
56 // if we don't have a resource name then we don't know how to label the
57 // data and should abort.
58 if (resourceName == nullptr) {
59 resetCurrentElement();
60 return;
61 }
62
63 // Only count elements that contain "size"; other values just provide metadata.
64 auto sizeResult = mCurrentValues.find("size");
65 if (sizeResult != mCurrentValues.end() && sizeResult->second.value > 0) {
66 if (!mIsCurrentValueWrapped) {
67 mTotalSize.value += sizeResult->second.value;
68 mTotalSize.count++;
69 }
70 } else {
71 resetCurrentElement();
72 return;
73 }
74
75 // find the purgeable size if one exists
76 auto purgeableResult = mCurrentValues.find("purgeable_size");
77 if (!mIsCurrentValueWrapped && purgeableResult != mCurrentValues.end()) {
78 mPurgeableSize.value += purgeableResult->second.value;
79 mPurgeableSize.count++;
80 }
81
82 // do we store this element in the wrapped list or the skia managed list
83 auto& results = mIsCurrentValueWrapped ? mWrappedResults : mResults;
84
85 // insert a copy of the element and all of its keys. We must make a copy here instead of
86 // std::move() as we will continue to use these values later in the function and again
87 // when we move on to process the next element.
88 results.insert({mCurrentElement, mCurrentValues});
89
90 // insert the item into its mapped category
91 auto result = results.find(resourceName);
92 if (result != results.end()) {
93 auto& resourceValues = result->second;
94 auto totalResult = resourceValues.find(sizeResult->first);
95 if (totalResult != resourceValues.end()) {
96 ALOGE_IF(sizeResult->second.units != totalResult->second.units,
97 "resource units do not match so the sum of resource type (%s) will be invalid",
98 resourceName);
99 totalResult->second.value += sizeResult->second.value;
100 totalResult->second.count++;
101 } else {
102 ALOGE("an entry (%s) should not exist in the results without a size", resourceName);
103 }
104 } else {
105 // only store the size for the top level resource
106 results.insert({resourceName, {{sizeResult->first, sizeResult->second}}});
107 }
108
109 resetCurrentElement();
110 }
111
dumpNumericValue(const char * dumpName,const char * valueName,const char * units,uint64_t value)112 void SkiaMemoryReporter::dumpNumericValue(const char* dumpName, const char* valueName,
113 const char* units, uint64_t value) {
114 if (mCurrentElement != dumpName) {
115 processCurrentElement();
116 mCurrentElement = dumpName;
117 }
118 mCurrentValues.insert({valueName, {units, value}});
119 }
120
dumpWrappedState(const char * dumpName,bool isWrappedObject)121 void SkiaMemoryReporter::dumpWrappedState(const char* dumpName, bool isWrappedObject) {
122 if (mCurrentElement != dumpName) {
123 processCurrentElement();
124 mCurrentElement = dumpName;
125 }
126 mIsCurrentValueWrapped = isWrappedObject;
127 }
128
logOutput(std::string & log,bool wrappedResources)129 void SkiaMemoryReporter::logOutput(std::string& log, bool wrappedResources) {
130 // process the current element before logging
131 processCurrentElement();
132
133 const auto& resultsMap = wrappedResources ? mWrappedResults : mResults;
134
135 // log each individual element based on the resource map
136 for (const auto& resourceCategory : mResourceMap) {
137 // find the named item and print the totals
138 const auto categoryItem = resultsMap.find(resourceCategory.second);
139 if (categoryItem != resultsMap.end()) {
140 auto result = categoryItem->second.find("size");
141 if (result != categoryItem->second.end()) {
142 TraceValue traceValue = convertUnits(result->second);
143 const char* entry = (traceValue.count > 1) ? "entries" : "entry";
144 StringAppendF(&log, " %s: %.2f %s (%d %s)\n", categoryItem->first.c_str(),
145 traceValue.value, traceValue.units, traceValue.count, entry);
146 }
147 if (mItemize) {
148 for (const auto& individualItem : resultsMap) {
149 // if the individual item matches the category then print all its details or
150 // in the case of wrapped resources just print the wrapped size
151 const char* categoryMatch = mapName(individualItem.first.c_str());
152 if (categoryMatch && strcmp(categoryMatch, resourceCategory.second) == 0) {
153 auto result = individualItem.second.find("size");
154 TraceValue size = convertUnits(result->second);
155 StringAppendF(&log, " %s: size[%.2f %s]", individualItem.first.c_str(),
156 size.value, size.units);
157 if (!wrappedResources) {
158 for (const auto& itemValues : individualItem.second) {
159 if (strcmp("size", itemValues.first) == 0) {
160 continue;
161 }
162 TraceValue traceValue = convertUnits(itemValues.second);
163 if (traceValue.value == 0.0f) {
164 StringAppendF(&log, " %s[%s]", itemValues.first,
165 traceValue.units);
166 } else {
167 StringAppendF(&log, " %s[%.2f %s]", itemValues.first,
168 traceValue.value, traceValue.units);
169 }
170 }
171 }
172 StringAppendF(&log, "\n");
173 }
174 }
175 }
176 }
177 }
178 }
179
logTotals(std::string & log)180 void SkiaMemoryReporter::logTotals(std::string& log) {
181 // process the current element before logging
182 processCurrentElement();
183
184 TraceValue total = convertUnits(mTotalSize);
185 TraceValue purgeable = convertUnits(mPurgeableSize);
186 StringAppendF(&log, " %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
187 total.value, total.units, purgeable.value, purgeable.units);
188 }
189
convertUnits(const TraceValue & value)190 SkiaMemoryReporter::TraceValue SkiaMemoryReporter::convertUnits(const TraceValue& value) {
191 TraceValue output(value);
192 if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
193 output.value = output.value / 1024.0f;
194 output.units = "KB";
195 }
196 if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
197 output.value = output.value / 1024.0f;
198 output.units = "MB";
199 }
200 return output;
201 }
202
203 } /* namespace skia */
204 } /* namespace renderengine */
205 } /* namespace android */
206