Home | History | Annotate | Download | only in skia
      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