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