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