blob: 27bfc95d7ac6621f8fe4ffec590bdc3a8e47f646 [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <benchmark/benchmark.h>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "utils.h"
namespace android {
namespace os {
namespace statsd {
namespace {
const int kMaxAtomId = 100000;
const int kMaxErrorCode = 20;
std::vector<std::pair<std::vector<int>, std::vector<int>>> generateIdsAndErrorsVectors(
const std::vector<int>& idsCounts, const std::vector<int>& errorsCounts) {
std::vector<std::pair<std::vector<int>, std::vector<int>>> result;
for (const int idCount : idsCounts) {
for (const int errorCount : errorsCounts) {
auto ids = generateRandomIds(idCount, kMaxAtomId);
auto errors = generateRandomIds(errorCount, kMaxErrorCode);
result.push_back(std::make_pair(ids, errors));
}
}
return result;
}
const std::vector<std::pair<std::vector<int>, std::vector<int>>> kRandomIdsAndErrorsPairs =
generateIdsAndErrorsVectors({1, 5, 10, 50}, {1, 2, 5, 10});
struct TestVector {
std::vector<int> errors;
std::vector<int> tags;
};
std::vector<TestVector> generateTestVector(
int count,
const std::vector<std::pair<std::vector<int>, std::vector<int>>>& idsAndErrorsPairs) {
std::srand(std::time(nullptr));
std::vector<TestVector> result;
for (const auto& idsAndErrors : idsAndErrorsPairs) {
TestVector testVector;
testVector.errors.reserve(count);
testVector.tags.reserve(count);
for (int i = 0; i < count; i++) {
const int randomAtomIdFromReferenceList =
idsAndErrors.first[std::rand() % idsAndErrors.first.size()];
const int randomErrorFromReferenceList =
idsAndErrors.second[std::rand() % idsAndErrors.second.size()];
testVector.errors.push_back(randomErrorFromReferenceList);
testVector.tags.push_back(randomAtomIdFromReferenceList);
}
result.push_back(testVector);
}
return result;
}
constexpr int kTestVectorSize = 4096;
constexpr int kMaxAtomTagsCount = 100;
const std::vector<TestVector> kTestVectors =
generateTestVector(kTestVectorSize, kRandomIdsAndErrorsPairs);
struct LossInfoVector {
// using vectors is more memory efficient
// using vectors fits well with the dump API implementation - no need to transform data
// before writing into AStatsEvent since it is aligned with repeated int32 fields
std::vector<int> errors;
std::vector<int> tags;
std::vector<int> counts;
bool noteLossInfo(int error, int tag) {
// linear search is Ok here since we do not expect to see many tags, usually 1-5 per uid
// exception is system server where we see 10s atoms
size_t locatedTagIndex = 0;
for (locatedTagIndex = 0; locatedTagIndex < errors.size(); ++locatedTagIndex) {
// is there already logged an atom with tag == atomId
if (errors[locatedTagIndex] == error && tags[locatedTagIndex] == tag) {
++counts[locatedTagIndex];
return true;
}
}
// if pair [error, atomId] is not found and guardrail is not reached yet store loss
// counter
if (locatedTagIndex == errors.size() && tags.size() < kMaxAtomTagsCount) {
errors.push_back(error);
tags.push_back(tag);
counts.push_back(1);
} else {
return false;
}
return true;
}
};
using LossInfoKey = std::pair<int, int>; // [error, tag]
template <typename T>
struct LossInfoMap {
// using maps is more CPU efficient however will require some postprocessing before
// writing into the socket
T countsPerErrorTag;
bool noteLossInfo(int error, int tag) {
LossInfoKey key = std::make_pair(error, tag);
auto counterIt = countsPerErrorTag.find(key);
if (counterIt != countsPerErrorTag.end()) {
++counterIt->second;
} else if (countsPerErrorTag.size() < kMaxAtomTagsCount) {
countsPerErrorTag[key] = 1;
} else {
return false;
}
return true;
}
};
} // namespace
struct hash_pair final {
template <class TFirst, class TSecond>
size_t operator()(const std::pair<TFirst, TSecond>& p) const noexcept {
uintmax_t hash = std::hash<TFirst>{}(p.first);
hash <<= sizeof(uintmax_t) * 4;
hash ^= std::hash<TSecond>{}(p.second);
return std::hash<uintmax_t>{}(hash);
}
};
static void BM_LossInfoCollectionAndDumpViaVector(benchmark::State& state) {
const TestVector& testVector = kTestVectors[state.range(0)];
LossInfoVector lossInfo;
while (state.KeepRunning()) {
int res = 0;
for (int i = 0; i < kTestVectorSize; i++) {
res += lossInfo.noteLossInfo(testVector.errors[i], testVector.tags[i]);
}
benchmark::DoNotOptimize(res);
}
}
BENCHMARK(BM_LossInfoCollectionAndDumpViaVector)
->Args({0})
->Args({1})
->Args({2})
->Args({3})
->Args({4})
->Args({5})
->Args({6})
->Args({7})
->Args({8})
->Args({9})
->Args({10})
->Args({11})
->Args({12})
->Args({13})
->Args({14})
->Args({15});
static void BM_LossInfoCollectionAndDumpViaUnorderedMap(benchmark::State& state) {
const TestVector& testVector = kTestVectors[state.range(0)];
LossInfoMap<std::unordered_map<LossInfoKey, int, hash_pair>> lossInfo;
while (state.KeepRunning()) {
int res = 0;
for (int i = 0; i < kTestVectorSize; i++) {
res += lossInfo.noteLossInfo(testVector.errors[i], testVector.tags[i]);
}
benchmark::DoNotOptimize(res);
}
}
BENCHMARK(BM_LossInfoCollectionAndDumpViaUnorderedMap)
->Args({0})
->Args({1})
->Args({2})
->Args({3})
->Args({4})
->Args({5})
->Args({6})
->Args({7})
->Args({8})
->Args({9})
->Args({10})
->Args({11})
->Args({12})
->Args({13})
->Args({14})
->Args({15});
} // namespace statsd
} // namespace os
} // namespace android