1 /*
2  * Copyright (C) 2023 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 #include <cstdlib>
17 #include <ctime>
18 #include <map>
19 #include <unordered_map>
20 #include <unordered_set>
21 #include <vector>
22 
23 #include "benchmark/benchmark.h"
24 
25 namespace android {
26 namespace os {
27 namespace statsd {
28 
29 namespace {
30 
generateRandomIds(int count,int maxRange)31 std::vector<int> generateRandomIds(int count, int maxRange) {
32     std::srand(std::time(nullptr));
33 
34     std::unordered_set<int> unique_values;
35 
36     while (unique_values.size() <= count) {
37         unique_values.insert(std::rand() % maxRange);
38     }
39 
40     std::vector<int> result(unique_values.begin(), unique_values.end());
41 
42     return result;
43 }
44 
45 const int kMaxAtomId = 100000;
46 const int kMaxErrorCode = 20;
47 
generateIdsAndErrorsVectors(const std::vector<int> & idsCounts,const std::vector<int> & errorsCounts)48 std::vector<std::pair<std::vector<int>, std::vector<int>>> generateIdsAndErrorsVectors(
49         const std::vector<int>& idsCounts, const std::vector<int>& errorsCounts) {
50     std::vector<std::pair<std::vector<int>, std::vector<int>>> result;
51     for (const int idCount : idsCounts) {
52         for (const int errorCount : errorsCounts) {
53             auto ids = generateRandomIds(idCount, kMaxAtomId);
54             auto errors = generateRandomIds(errorCount, kMaxErrorCode);
55             result.push_back(std::make_pair(ids, errors));
56         }
57     }
58     return result;
59 }
60 
61 const std::vector<std::pair<std::vector<int>, std::vector<int>>> kRandomIdsAndErrorsPairs =
62         generateIdsAndErrorsVectors({1, 5, 10, 50}, {1, 2, 5, 10});
63 
64 struct TestVector {
65     std::vector<int> errors;
66     std::vector<int> tags;
67 };
68 
generateTestVector(int count,const std::vector<std::pair<std::vector<int>,std::vector<int>>> & idsAndErrorsPairs)69 std::vector<TestVector> generateTestVector(
70         int count,
71         const std::vector<std::pair<std::vector<int>, std::vector<int>>>& idsAndErrorsPairs) {
72     std::srand(std::time(nullptr));
73 
74     std::vector<TestVector> result;
75 
76     for (const auto& idsAndErrors : idsAndErrorsPairs) {
77         TestVector testVector;
78 
79         testVector.errors.reserve(count);
80         testVector.tags.reserve(count);
81 
82         for (int i = 0; i < count; i++) {
83             const int randomAtomIdFromReferenceList =
84                     idsAndErrors.first[std::rand() % idsAndErrors.first.size()];
85             const int randomErrorFromReferenceList =
86                     idsAndErrors.second[std::rand() % idsAndErrors.second.size()];
87 
88             testVector.errors.push_back(randomErrorFromReferenceList);
89             testVector.tags.push_back(randomAtomIdFromReferenceList);
90         }
91         result.push_back(testVector);
92     }
93 
94     return result;
95 }
96 
97 constexpr int kTestVectorSize = 4096;
98 constexpr int kMaxAtomTagsCount = 100;
99 
100 const std::vector<TestVector> kTestVectors =
101         generateTestVector(kTestVectorSize, kRandomIdsAndErrorsPairs);
102 
103 struct LossInfoVector {
104     // using vectors is more memory efficient
105     // using vectors fits well with the dump API implementation - no need to transform data
106     // before writing into AStatsEvent since it is aligned with repeated int32 fields
107     std::vector<int> errors;
108     std::vector<int> tags;
109     std::vector<int> counts;
110 
noteLossInfoandroid::os::statsd::__anon3b2217f20111::LossInfoVector111     bool noteLossInfo(int error, int tag) {
112         // linear search is Ok here since we do not expect to see many tags, usually 1-5 per uid
113         // exception is system server where we see 10s atoms
114         size_t locatedTagIndex = 0;
115         for (locatedTagIndex = 0; locatedTagIndex < errors.size(); ++locatedTagIndex) {
116             // is there already logged an atom with tag == atomId
117             if (errors[locatedTagIndex] == error && tags[locatedTagIndex] == tag) {
118                 ++counts[locatedTagIndex];
119                 return true;
120             }
121         }
122 
123         // if pair [error, atomId] is not found and guardrail is not reached yet store loss
124         // counter
125         if (locatedTagIndex == errors.size() && tags.size() < kMaxAtomTagsCount) {
126             errors.push_back(error);
127             tags.push_back(tag);
128             counts.push_back(1);
129         } else {
130             return false;
131         }
132         return true;
133     }
134 };
135 
136 using LossInfoKey = std::pair<int, int>;  // [error, tag]
137 
138 template <typename T>
139 struct LossInfoMap {
140     // using maps is more CPU efficient however will require some postprocessing before
141     // writing into the socket
142     T countsPerErrorTag;
143 
noteLossInfoandroid::os::statsd::__anon3b2217f20111::LossInfoMap144     bool noteLossInfo(int error, int tag) {
145         LossInfoKey key = std::make_pair(error, tag);
146         auto counterIt = countsPerErrorTag.find(key);
147 
148         if (counterIt != countsPerErrorTag.end()) {
149             ++counterIt->second;
150         } else if (countsPerErrorTag.size() < kMaxAtomTagsCount) {
151             countsPerErrorTag[key] = 1;
152         } else {
153             return false;
154         }
155 
156         return true;
157     }
158 };
159 
160 }  // namespace
161 
162 struct hash_pair final {
163     template <class TFirst, class TSecond>
operator ()android::os::statsd::hash_pair164     size_t operator()(const std::pair<TFirst, TSecond>& p) const noexcept {
165         uintmax_t hash = std::hash<TFirst>{}(p.first);
166         hash <<= sizeof(uintmax_t) * 4;
167         hash ^= std::hash<TSecond>{}(p.second);
168         return std::hash<uintmax_t>{}(hash);
169     }
170 };
171 
BM_LossInfoCollectionAndDumpViaVector(benchmark::State & state)172 static void BM_LossInfoCollectionAndDumpViaVector(benchmark::State& state) {
173     const TestVector& testVector = kTestVectors[state.range(0)];
174     LossInfoVector lossInfo;
175 
176     while (state.KeepRunning()) {
177         int res = 0;
178         for (int i = 0; i < kTestVectorSize; i++) {
179             res += lossInfo.noteLossInfo(testVector.errors[i], testVector.tags[i]);
180         }
181         benchmark::DoNotOptimize(res);
182     }
183 }
184 BENCHMARK(BM_LossInfoCollectionAndDumpViaVector)
185         ->Args({0})
186         ->Args({1})
187         ->Args({2})
188         ->Args({3})
189         ->Args({4})
190         ->Args({5})
191         ->Args({6})
192         ->Args({7})
193         ->Args({8})
194         ->Args({9})
195         ->Args({10})
196         ->Args({11})
197         ->Args({12})
198         ->Args({13})
199         ->Args({14})
200         ->Args({15});
201 
BM_LossInfoCollectionAndDumpViaUnorderedMap(benchmark::State & state)202 static void BM_LossInfoCollectionAndDumpViaUnorderedMap(benchmark::State& state) {
203     const TestVector& testVector = kTestVectors[state.range(0)];
204     LossInfoMap<std::unordered_map<LossInfoKey, int, hash_pair>> lossInfo;
205 
206     while (state.KeepRunning()) {
207         int res = 0;
208         for (int i = 0; i < kTestVectorSize; i++) {
209             res += lossInfo.noteLossInfo(testVector.errors[i], testVector.tags[i]);
210         }
211         benchmark::DoNotOptimize(res);
212     }
213 }
214 BENCHMARK(BM_LossInfoCollectionAndDumpViaUnorderedMap)
215         ->Args({0})
216         ->Args({1})
217         ->Args({2})
218         ->Args({3})
219         ->Args({4})
220         ->Args({5})
221         ->Args({6})
222         ->Args({7})
223         ->Args({8})
224         ->Args({9})
225         ->Args({10})
226         ->Args({11})
227         ->Args({12})
228         ->Args({13})
229         ->Args({14})
230         ->Args({15});
231 
232 }  //  namespace statsd
233 }  //  namespace os
234 }  //  namespace android
235