• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 "SkiaMemoryTracer.h"
18 
19 namespace android {
20 namespace uirenderer {
21 namespace skiapipeline {
22 
SkiaMemoryTracer(std::vector<ResourcePair> resourceMap,bool itemizeType)23 SkiaMemoryTracer::SkiaMemoryTracer(std::vector<ResourcePair> resourceMap, bool itemizeType)
24         : mResourceMap(resourceMap)
25         , mItemizeType(itemizeType)
26         , mTotalSize("bytes", 0)
27         , mPurgeableSize("bytes", 0) {}
28 
SkiaMemoryTracer(const char * categoryKey,bool itemizeType)29 SkiaMemoryTracer::SkiaMemoryTracer(const char* categoryKey, bool itemizeType)
30         : mCategoryKey(categoryKey)
31         , mItemizeType(itemizeType)
32         , mTotalSize("bytes", 0)
33         , mPurgeableSize("bytes", 0) {}
34 
mapName(const char * resourceName)35 const char* SkiaMemoryTracer::mapName(const char* resourceName) {
36     for (auto& resource : mResourceMap) {
37         if (SkStrContains(resourceName, resource.first)) {
38             return resource.second;
39         }
40     }
41     return nullptr;
42 }
43 
processElement()44 void SkiaMemoryTracer::processElement() {
45     if (!mCurrentElement.empty()) {
46         // Only count elements that contain "size", other values just provide metadata.
47         auto sizeResult = mCurrentValues.find("size");
48         if (sizeResult != mCurrentValues.end()) {
49             mTotalSize.value += sizeResult->second.value;
50             mTotalSize.count++;
51         } else {
52             mCurrentElement.clear();
53             mCurrentValues.clear();
54             return;
55         }
56 
57         // find the purgeable size if one exists
58         auto purgeableResult = mCurrentValues.find("purgeable_size");
59         if (purgeableResult != mCurrentValues.end()) {
60             mPurgeableSize.value += purgeableResult->second.value;
61             mPurgeableSize.count++;
62         }
63 
64         // find the type if one exists
65         const char* type;
66         auto typeResult = mCurrentValues.find("type");
67         if (typeResult != mCurrentValues.end()) {
68             type = typeResult->second.units;
69         } else if (mItemizeType) {
70             type = "Other";
71         }
72 
73         // compute the type if we are itemizing or use the default "size" if we are not
74         const char* key = (mItemizeType) ? type : sizeResult->first;
75         SkASSERT(key != nullptr);
76 
77         // compute the top level element name using either the map or category key
78         const char* resourceName = mapName(mCurrentElement.c_str());
79         if (mCategoryKey != nullptr) {
80             // find the category if one exists
81             auto categoryResult = mCurrentValues.find(mCategoryKey);
82             if (categoryResult != mCurrentValues.end()) {
83                 resourceName = categoryResult->second.units;
84             } else if (mItemizeType) {
85                 resourceName = "Other";
86             }
87         }
88 
89         // if we don't have a pretty name then use the dumpName
90         if (resourceName == nullptr) {
91             resourceName = mCurrentElement.c_str();
92         }
93 
94         auto result = mResults.find(resourceName);
95         if (result != mResults.end()) {
96             auto& resourceValues = result->second;
97             typeResult = resourceValues.find(key);
98             if (typeResult != resourceValues.end()) {
99                 SkASSERT(sizeResult->second.units == typeResult->second.units);
100                 typeResult->second.value += sizeResult->second.value;
101                 typeResult->second.count++;
102             } else {
103                 resourceValues.insert({key, sizeResult->second});
104             }
105         } else {
106             TraceValue sizeValue = sizeResult->second;
107             mCurrentValues.clear();
108             mCurrentValues.insert({key, sizeValue});
109             mResults.insert({resourceName, mCurrentValues});
110         }
111     }
112 
113     mCurrentElement.clear();
114     mCurrentValues.clear();
115 }
116 
dumpNumericValue(const char * dumpName,const char * valueName,const char * units,uint64_t value)117 void SkiaMemoryTracer::dumpNumericValue(const char* dumpName, const char* valueName,
118                                         const char* units, uint64_t value) {
119     if (mCurrentElement != dumpName) {
120         processElement();
121         mCurrentElement = dumpName;
122     }
123     mCurrentValues.insert({valueName, {units, value}});
124 }
125 
hasOutput()126 bool SkiaMemoryTracer::hasOutput() {
127     // process any remaining elements
128     processElement();
129     return mResults.size() > 0;
130 }
131 
logOutput(String8 & log)132 void SkiaMemoryTracer::logOutput(String8& log) {
133     // process any remaining elements
134     processElement();
135 
136     for (const auto& namedItem : mResults) {
137         if (mItemizeType) {
138             log.appendFormat("  %s:\n", namedItem.first.c_str());
139             for (const auto& typedValue : namedItem.second) {
140                 TraceValue traceValue = convertUnits(typedValue.second);
141                 const char* entry = (traceValue.count > 1) ? "entries" : "entry";
142                 log.appendFormat("    %s: %.2f %s (%d %s)\n", typedValue.first, traceValue.value,
143                                  traceValue.units, traceValue.count, entry);
144             }
145         } else {
146             auto result = namedItem.second.find("size");
147             if (result != namedItem.second.end()) {
148                 TraceValue traceValue = convertUnits(result->second);
149                 const char* entry = (traceValue.count > 1) ? "entries" : "entry";
150                 log.appendFormat("  %s: %.2f %s (%d %s)\n", namedItem.first.c_str(),
151                                  traceValue.value, traceValue.units, traceValue.count, entry);
152             }
153         }
154     }
155 }
156 
total()157 size_t SkiaMemoryTracer::total() {
158     processElement();
159     if (!strcmp("bytes", mTotalSize.units)) {
160         return mTotalSize.value;
161     }
162     return 0;
163 }
164 
logTotals(String8 & log)165 void SkiaMemoryTracer::logTotals(String8& log) {
166     TraceValue total = convertUnits(mTotalSize);
167     TraceValue purgeable = convertUnits(mPurgeableSize);
168     log.appendFormat("  %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
169                      total.value, total.units, purgeable.value, purgeable.units);
170 }
171 
convertUnits(const TraceValue & value)172 SkiaMemoryTracer::TraceValue SkiaMemoryTracer::convertUnits(const TraceValue& value) {
173     TraceValue output(value);
174     if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
175         output.value = output.value / 1024.0f;
176         output.units = "KB";
177     }
178     if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
179         output.value = output.value / 1024.0f;
180         output.units = "MB";
181     }
182     return output;
183 }
184 
185 } /* namespace skiapipeline */
186 } /* namespace uirenderer */
187 } /* namespace android */
188