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 mCurrentValues.clear(); 110 mCurrentValues.insert({key, sizeResult->second}); 111 mResults.insert({resourceName, mCurrentValues}); 112 } 113 } 114 115 mCurrentElement.clear(); 116 mCurrentValues.clear(); 117 } 118 119 void SkiaMemoryTracer::dumpNumericValue(const char* dumpName, const char* valueName, 120 const char* units, uint64_t value) { 121 if (mCurrentElement != dumpName) { 122 processElement(); 123 mCurrentElement = dumpName; 124 } 125 mCurrentValues.insert({valueName, {units, value}}); 126 } 127 128 void SkiaMemoryTracer::logOutput(String8& log) { 129 // process any remaining elements 130 processElement(); 131 132 for (const auto& namedItem : mResults) { 133 if (mItemizeType) { 134 log.appendFormat(" %s:\n", namedItem.first.c_str()); 135 for (const auto& typedValue : namedItem.second) { 136 TraceValue traceValue = convertUnits(typedValue.second); 137 const char* entry = (traceValue.count > 1) ? "entries" : "entry"; 138 log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first, 139 traceValue.value, traceValue.units, traceValue.count, entry); 140 } 141 } else { 142 auto result = namedItem.second.find("size"); 143 if (result != namedItem.second.end()) { 144 TraceValue traceValue = convertUnits(result->second); 145 const char* entry = (traceValue.count > 1) ? "entries" : "entry"; 146 log.appendFormat(" %s: %.2f %s (%d %s)\n", namedItem.first.c_str(), 147 traceValue.value, traceValue.units, traceValue.count, entry); 148 } 149 } 150 } 151 } 152 153 void SkiaMemoryTracer::logTotals(String8& log) { 154 TraceValue total = convertUnits(mTotalSize); 155 TraceValue purgeable = convertUnits(mPurgeableSize); 156 log.appendFormat(" %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value, 157 total.value, total.units, purgeable.value, purgeable.units); 158 } 159 160 SkiaMemoryTracer::TraceValue SkiaMemoryTracer::convertUnits(const TraceValue& value) { 161 TraceValue output(value); 162 if (SkString("bytes") == SkString(output.units) && output.value >= 1024) { 163 output.value = output.value / 1024.0f; 164 output.units = "KB"; 165 } 166 if (SkString("KB") == SkString(output.units) && output.value >= 1024) { 167 output.value = output.value / 1024.0f; 168 output.units = "MB"; 169 } 170 return output; 171 } 172 173 } /* namespace skiapipeline */ 174 } /* namespace uirenderer */ 175 } /* namespace android */ 176