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
19namespace android {
20namespace uirenderer {
21namespace skiapipeline {
22
23SkiaMemoryTracer::SkiaMemoryTracer(std::vector<ResourcePair> resourceMap, bool itemizeType)
24            : mResourceMap(resourceMap)
25            , mItemizeType(itemizeType)
26            , mTotalSize("bytes", 0)
27            , mPurgeableSize("bytes", 0) {}
28
29SkiaMemoryTracer::SkiaMemoryTracer(const char* categoryKey, bool itemizeType)
30            : mCategoryKey(categoryKey)
31            , mItemizeType(itemizeType)
32            , mTotalSize("bytes", 0)
33            , mPurgeableSize("bytes", 0) {}
34
35const 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
44void 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
119void 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
128void 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
153void 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
160SkiaMemoryTracer::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