Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
test_hot_path_helper.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
10#include <gtest/gtest.h>
12
13#include <atomic>
14#include <memory>
15#include <string>
16#include <thread>
17#include <unordered_map>
18#include <vector>
19
20using namespace kcenon::monitoring::hot_path;
21
22namespace {
23
24struct TestData {
25 int value{0};
26 std::string name;
27};
28
29} // namespace
30
31class HotPathHelperTest : public ::testing::Test {
32protected:
33 void SetUp() override {
34 map_.clear();
35 }
36
37 void TearDown() override {}
38
39 std::unordered_map<std::string, std::unique_ptr<TestData>> map_;
40 std::shared_mutex mutex_;
41};
42
43// =========================================================================
44// get_or_create Tests
45// =========================================================================
46
47TEST_F(HotPathHelperTest, GetOrCreateNewEntry) {
48 auto* ptr = get_or_create(
49 map_, mutex_, "key1",
50 []() { return std::make_unique<TestData>(); });
51
52 ASSERT_NE(ptr, nullptr);
53 EXPECT_EQ(map_.size(), 1u);
54 EXPECT_EQ(ptr->value, 0);
55}
56
57TEST_F(HotPathHelperTest, GetOrCreateExistingEntry) {
58 // Pre-create an entry
59 map_["key1"] = std::make_unique<TestData>();
60 map_["key1"]->value = 42;
61
62 int create_count = 0;
63 auto* ptr = get_or_create(
64 map_, mutex_, "key1",
65 [&create_count]() {
66 ++create_count;
67 return std::make_unique<TestData>();
68 });
69
70 ASSERT_NE(ptr, nullptr);
71 EXPECT_EQ(map_.size(), 1u);
72 EXPECT_EQ(ptr->value, 42);
73 EXPECT_EQ(create_count, 0); // Factory should not be called
74}
75
76TEST_F(HotPathHelperTest, GetOrCreateMultipleKeys) {
77 auto* ptr1 = get_or_create(
78 map_, mutex_, "key1",
79 []() { return std::make_unique<TestData>(); });
80 auto* ptr2 = get_or_create(
81 map_, mutex_, "key2",
82 []() { return std::make_unique<TestData>(); });
83
84 ASSERT_NE(ptr1, nullptr);
85 ASSERT_NE(ptr2, nullptr);
86 EXPECT_NE(ptr1, ptr2);
87 EXPECT_EQ(map_.size(), 2u);
88}
89
90// =========================================================================
91// get_or_create_with_init Tests
92// =========================================================================
93
94TEST_F(HotPathHelperTest, GetOrCreateWithInitNewEntry) {
95 auto* ptr = get_or_create_with_init(
96 map_, mutex_, "key1",
97 []() { return std::make_unique<TestData>(); },
98 [](TestData& d) {
99 d.value = 100;
100 d.name = "initialized";
101 });
102
103 ASSERT_NE(ptr, nullptr);
104 EXPECT_EQ(ptr->value, 100);
105 EXPECT_EQ(ptr->name, "initialized");
106}
107
108TEST_F(HotPathHelperTest, GetOrCreateWithInitExistingEntry) {
109 // Pre-create an entry
110 map_["key1"] = std::make_unique<TestData>();
111 map_["key1"]->value = 42;
112 map_["key1"]->name = "original";
113
114 int init_count = 0;
115 auto* ptr = get_or_create_with_init(
116 map_, mutex_, "key1",
117 []() { return std::make_unique<TestData>(); },
118 [&init_count](TestData& d) {
119 ++init_count;
120 d.value = 100;
121 });
122
123 ASSERT_NE(ptr, nullptr);
124 EXPECT_EQ(ptr->value, 42); // Should not be modified
125 EXPECT_EQ(ptr->name, "original");
126 EXPECT_EQ(init_count, 0); // Init should not be called
127}
128
129// =========================================================================
130// get_or_create_and_update Tests
131// =========================================================================
132
133TEST_F(HotPathHelperTest, GetOrCreateAndUpdateNewEntry) {
134 int result = get_or_create_and_update(
135 map_, mutex_, "key1",
136 []() { return std::make_unique<TestData>(); },
137 [](TestData& d) {
138 d.value = 42;
139 return d.value;
140 });
141
142 EXPECT_EQ(result, 42);
143 EXPECT_EQ(map_["key1"]->value, 42);
144}
145
146TEST_F(HotPathHelperTest, GetOrCreateAndUpdateExistingEntry) {
147 // Pre-create an entry
148 map_["key1"] = std::make_unique<TestData>();
149 map_["key1"]->value = 10;
150
151 int result = get_or_create_and_update(
152 map_, mutex_, "key1",
153 []() { return std::make_unique<TestData>(); },
154 [](TestData& d) {
155 d.value += 5;
156 return d.value;
157 });
158
159 EXPECT_EQ(result, 15);
160 EXPECT_EQ(map_["key1"]->value, 15);
161}
162
163// =========================================================================
164// Concurrent Access Tests
165// =========================================================================
166
167TEST_F(HotPathHelperTest, ConcurrentGetOrCreate) {
168 std::atomic<int> create_count{0};
169 const int num_threads = 10;
170 const int iterations_per_thread = 1000;
171
172 std::vector<std::thread> threads;
173 threads.reserve(num_threads);
174
175 for (int t = 0; t < num_threads; ++t) {
176 threads.emplace_back([this, &create_count, iterations_per_thread]() {
177 for (int i = 0; i < iterations_per_thread; ++i) {
178 auto* ptr = get_or_create(
179 map_, mutex_, "shared_key",
180 [&create_count]() {
181 ++create_count;
182 return std::make_unique<TestData>();
183 });
184 ASSERT_NE(ptr, nullptr);
185 }
186 });
187 }
188
189 for (auto& t : threads) {
190 t.join();
191 }
192
193 EXPECT_EQ(map_.size(), 1u);
194 EXPECT_EQ(create_count.load(), 1); // Only one creation should occur
195}
196
197TEST_F(HotPathHelperTest, ConcurrentDifferentKeys) {
198 std::atomic<int> total_creates{0};
199 const int num_threads = 10;
200 const int keys_per_thread = 100;
201
202 std::vector<std::thread> threads;
203 threads.reserve(num_threads);
204
205 for (int t = 0; t < num_threads; ++t) {
206 threads.emplace_back([this, t, &total_creates, keys_per_thread]() {
207 for (int i = 0; i < keys_per_thread; ++i) {
208 std::string key = "key_" + std::to_string(t) + "_" + std::to_string(i);
209 auto* ptr = get_or_create(
210 map_, mutex_, key,
211 [&total_creates]() {
212 ++total_creates;
213 return std::make_unique<TestData>();
214 });
215 ASSERT_NE(ptr, nullptr);
216 }
217 });
218 }
219
220 for (auto& t : threads) {
221 t.join();
222 }
223
224 EXPECT_EQ(map_.size(), static_cast<size_t>(num_threads * keys_per_thread));
225 EXPECT_EQ(total_creates.load(), num_threads * keys_per_thread);
226}
227
228TEST_F(HotPathHelperTest, ConcurrentMixedReadWrite) {
229 // Pre-create some entries
230 for (int i = 0; i < 50; ++i) {
231 map_["existing_" + std::to_string(i)] = std::make_unique<TestData>();
232 }
233
234 const int num_threads = 8;
235 const int iterations = 500;
236
237 std::vector<std::thread> threads;
238 threads.reserve(num_threads);
239
240 for (int t = 0; t < num_threads; ++t) {
241 threads.emplace_back([this, t, iterations]() {
242 for (int i = 0; i < iterations; ++i) {
243 // Half the time access existing, half the time create new
244 std::string key = (i % 2 == 0)
245 ? "existing_" + std::to_string(i % 50)
246 : "new_" + std::to_string(t) + "_" + std::to_string(i);
247
248 auto* ptr = get_or_create(
249 map_, mutex_, key,
250 []() { return std::make_unique<TestData>(); });
251 ASSERT_NE(ptr, nullptr);
252 }
253 });
254 }
255
256 for (auto& t : threads) {
257 t.join();
258 }
259
260 EXPECT_GE(map_.size(), 50u); // At least the pre-existing entries
261}
262
263// =========================================================================
264// Performance Characteristics Test
265// =========================================================================
266
267TEST_F(HotPathHelperTest, HotPathOptimizationVerification) {
268 // Create entry first
269 map_["hot_key"] = std::make_unique<TestData>();
270
271 std::atomic<int> create_calls{0};
272 const int hot_path_iterations = 10000;
273
274 // Simulate hot path - many reads, no creates
275 for (int i = 0; i < hot_path_iterations; ++i) {
276 auto* ptr = get_or_create(
277 map_, mutex_, "hot_key",
278 [&create_calls]() {
279 ++create_calls;
280 return std::make_unique<TestData>();
281 });
282 ASSERT_NE(ptr, nullptr);
283 }
284
285 // Verify factory was never called (hot path optimization working)
286 EXPECT_EQ(create_calls.load(), 0);
287}
std::unordered_map< std::string, std::unique_ptr< TestData > > map_
std::shared_mutex mutex_
Reusable hot-path optimization patterns for concurrent map access.
auto get_or_create_with_init(Map &map, std::shared_mutex &mutex, const Key &key, CreateFn create_fn, InitFn init_fn) -> typename std::remove_reference< decltype(*map.begin() ->second)>::type *
auto get_or_create_and_update(Map &map, std::shared_mutex &mutex, const Key &key, CreateFn create_fn, UpdateFn update_fn) -> decltype(update_fn(*map.begin() ->second))
auto get_or_create(Map &map, std::shared_mutex &mutex, const Key &key, CreateFn create_fn) -> typename std::remove_reference< decltype(*map.begin() ->second)>::type *
TEST_F(HotPathHelperTest, GetOrCreateNewEntry)