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