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 23 SkiaMemoryTracer::SkiaMemoryTracer(std::vector<ResourcePair> resourceMap, bool itemizeType) 24 : mResourceMap(resourceMap) 25 , mItemizeType(itemizeType) 26 , mTotalSize("bytes", 0) 27 , mPurgeableSize("bytes", 0) {} 28 29 SkiaMemoryTracer::SkiaMemoryTracer(const char* categoryKey, bool itemizeType) 30 : mCategoryKey(categoryKey) 31 , mItemizeType(itemizeType) 32 , mTotalSize("bytes", 0) 33 , mPurgeableSize("bytes", 0) {} 34 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 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 resource name then we don't know how to label the 90 // data and should abort. 91 if (resourceName == nullptr) { 92 mCurrentElement.clear(); 93 mCurrentValues.clear(); 94 return; 95 } 96 97 auto result = mResults.find(resourceName); 98 if (result != mResults.end()) { 99 auto& resourceValues = result->second; 100 typeResult = resourceValues.find(key); 101 if (typeResult != resourceValues.end()) { 102 SkASSERT(sizeResult->second.units == typeResult->second.units); 103 typeResult->second.value += sizeResult->second.value; 104 typeResult->second.count++; 105 } else { 106 resourceValues.insert({key, sizeResult->second}); 107 } 108 } else { 109 TraceValue sizeValue = sizeResult->second; 110 mCurrentValues.clear(); 111 mCurrentValues.insert({key, sizeValue}); 112 mResults.insert({resourceName, mCurrentValues}); 113 } 114 } 115 116 mCurrentElement.clear(); 117 mCurrentValues.clear(); 118 } 119 120 void SkiaMemoryTracer::dumpNumericValue(const char* dumpName, const char* valueName, 121 const char* units, uint64_t value) { 122 if (mCurrentElement != dumpName) { 123 processElement(); 124 mCurrentElement = dumpName; 125 } 126 mCurrentValues.insert({valueName, {units, value}}); 127 } 128 129 void SkiaMemoryTracer::logOutput(String8& log) { 130 // process any remaining elements 131 processElement(); 132 133 for (const auto& namedItem : mResults) { 134 if (mItemizeType) { 135 log.appendFormat(" %s:\n", namedItem.first.c_str()); 136 for (const auto& typedValue : namedItem.second) { 137 TraceValue traceValue = convertUnits(typedValue.second); 138 const char* entry = (traceValue.count > 1) ? "entries" : "entry"; 139 log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first, traceValue.value, 140 traceValue.units, traceValue.count, entry); 141 } 142 } else { 143 auto result = namedItem.second.find("size"); 144 if (result != namedItem.second.end()) { 145 TraceValue traceValue = convertUnits(result->second); 146 const char* entry = (traceValue.count > 1) ? "entries" : "entry"; 147 log.appendFormat(" %s: %.2f %s (%d %s)\n", namedItem.first.c_str(), 148 traceValue.value, traceValue.units, traceValue.count, entry); 149 } 150 } 151 } 152 } 153 154 void SkiaMemoryTracer::logTotals(String8& log) { 155 TraceValue total = convertUnits(mTotalSize); 156 TraceValue purgeable = convertUnits(mPurgeableSize); 157 log.appendFormat(" %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value, 158 total.value, total.units, purgeable.value, purgeable.units); 159 } 160 161 SkiaMemoryTracer::TraceValue SkiaMemoryTracer::convertUnits(const TraceValue& value) { 162 TraceValue output(value); 163 if (SkString("bytes") == SkString(output.units) && output.value >= 1024) { 164 output.value = output.value / 1024.0f; 165 output.units = "KB"; 166 } 167 if (SkString("KB") == SkString(output.units) && output.value >= 1024) { 168 output.value = output.value / 1024.0f; 169 output.units = "MB"; 170 } 171 return output; 172 } 173 174 } /* namespace skiapipeline */ 175 } /* namespace uirenderer */ 176 } /* namespace android */ 177