Skip to content

Commit a7dcd91

Browse files
committed
If the performance tracker (implemented as global singleton) is enabled and tests are run in parallel, race conditions may occur if more then one thread want to add a tracking entry (multiple concurrent push_back calls to a std::vector). To prevent this, a std::scoped_lock is inserted into each modifying performance tracker function. One downside being: the copy and move onstructors and assignment operators must now be deleted.
1 parent 8615532 commit a7dcd91

3 files changed

Lines changed: 48 additions & 144 deletions

File tree

include/plssvm/detail/tracking/performance_tracker.hpp

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
#include <chrono> // std::chrono::steady_clock::time_point
3838
#include <map> // std::map
39+
#include <mutex> // std::mutex, std::scoped_lock
3940
#include <optional> // std::optional
4041
#include <ostream> // std::ostream
4142
#include <string> // std::string
@@ -145,23 +146,23 @@ class performance_tracker {
145146
*/
146147
performance_tracker();
147148
/**
148-
* @brief Default copy-constructor. **Must** be implemented in .cpp file.
149+
* @brief Delete copy-constructor due to usage of a std::mutex.
149150
*/
150-
performance_tracker(const performance_tracker &);
151+
performance_tracker(const performance_tracker &) = delete;
151152
/**
152-
* @brief Default move-constructor. **Must** be implemented in .cpp file.
153+
* @brief Delete move-constructordue to usage of a std::mutex.
153154
*/
154-
performance_tracker(performance_tracker &&) noexcept;
155+
performance_tracker(performance_tracker &&) noexcept = delete;
155156
/**
156-
* @brief Default copy-assignment operator. **Must** be implemented in .cpp file.
157+
* @brief Delete copy-assignment operator due to usage of a std::mutex.
157158
* @return `*this`
158159
*/
159-
performance_tracker &operator=(const performance_tracker &);
160+
performance_tracker &operator=(const performance_tracker &) = delete;
160161
/**
161-
* @brief Default move-assignment operator. **Must** be implemented in .cpp file.
162+
* @brief Delete move-assignment operator due to usage of a std::mutex.
162163
* @return `*this`
163164
*/
164-
performance_tracker &operator=(performance_tracker &&) noexcept;
165+
performance_tracker &operator=(performance_tracker &&) noexcept = delete;
165166
/**
166167
* @brief Default destructor. **Must** be implemented in .cpp file.
167168
*/
@@ -299,12 +300,16 @@ class performance_tracker {
299300
events events_{};
300301
/// Flag indicating whether tracking is currently enabled or disabled. Tracking is enabled by default.
301302
bool is_tracking_{ true };
303+
/// Standard mutex to prevent race conditions if multiple threads try to add a tracking entry simultaneously.
304+
std::mutex mutex_;
302305
};
303306

304307
template <typename T>
305308
void performance_tracker::add_tracking_entry(const tracking_entry<T> &entry) {
306309
// check whether entries should currently be tracked
307310
if (this->is_tracking()) {
311+
const std::scoped_lock guard{ mutex_ };
312+
308313
std::string entry_value_str{};
309314
if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>) {
310315
// escape strings with "" since they may contain whitespaces
@@ -336,6 +341,8 @@ template <typename T>
336341
void performance_tracker::add_tracking_entry(const tracking_entry<std::vector<T>> &entry) {
337342
// check whether entries should currently be tracked
338343
if (this->is_tracking()) {
344+
const std::scoped_lock guard{ mutex_ };
345+
339346
std::string entry_value_str{};
340347
if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>) {
341348
// escape strings with "" since they may contain whitespaces

src/plssvm/detail/tracking/performance_tracker.cpp

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
#include <fstream> // std::ofstream
7575
#include <iostream> // std::ios_base::app, std::ostream, std::clog, std::endl
7676
#include <map> // std::map
77+
#include <mutex> // std::mutex, std::scoped_lock
7778
#include <string> // std::string
7879
#include <string_view> // std::string_view
7980
#include <utility> // std::move
@@ -135,15 +136,13 @@ namespace plssvm::detail::tracking {
135136

136137
// Must be explicitly defaulted in the cpp file to prevent linker errors!
137138
performance_tracker::performance_tracker() = default;
138-
performance_tracker::performance_tracker(const performance_tracker &) = default;
139-
performance_tracker::performance_tracker(performance_tracker &&) noexcept = default;
140-
performance_tracker &performance_tracker::operator=(const performance_tracker &) = default;
141-
performance_tracker &performance_tracker::operator=(performance_tracker &&) noexcept = default;
142139
performance_tracker::~performance_tracker() = default;
143140

144141
void performance_tracker::add_tracking_entry(const tracking_entry<plssvm::parameter> &entry) {
145142
// check whether entries should currently be tracked
146143
if (this->is_tracking()) {
144+
const std::scoped_lock guard{ mutex_ };
145+
147146
// create category
148147
tracking_entries_.emplace(entry.entry_category, std::map<std::string, std::vector<std::string>>{});
149148
// fill category with value
@@ -161,6 +160,8 @@ void performance_tracker::add_tracking_entry([[maybe_unused]] const tracking_ent
161160
#if defined(PLSSVM_HAS_MPI_ENABLED)
162161
// check whether entries should currently be tracked
163162
if (this->is_tracking()) {
163+
const std::scoped_lock guard{ mutex_ };
164+
164165
// create category
165166
tracking_entries_.emplace(entry.entry_category, std::map<std::string, std::vector<std::string>>{});
166167
// fill category with value
@@ -177,6 +178,8 @@ void performance_tracker::add_tracking_entry([[maybe_unused]] const tracking_ent
177178
void performance_tracker::add_tracking_entry(const tracking_entry<cmd::parser_train> &entry) {
178179
// check whether entries should currently be tracked
179180
if (this->is_tracking()) {
181+
const std::scoped_lock guard{ mutex_ };
182+
180183
// create category
181184
tracking_entries_.emplace(entry.entry_category, std::map<std::string, std::vector<std::string>>{});
182185
// fill category with value
@@ -204,6 +207,8 @@ void performance_tracker::add_tracking_entry(const tracking_entry<cmd::parser_tr
204207
void performance_tracker::add_tracking_entry(const tracking_entry<cmd::parser_predict> &entry) {
205208
// check whether entries should currently be tracked
206209
if (this->is_tracking()) {
210+
const std::scoped_lock guard{ mutex_ };
211+
207212
// create category
208213
tracking_entries_.emplace(entry.entry_category, std::map<std::string, std::vector<std::string>>{});
209214
// fill category with value
@@ -224,6 +229,8 @@ void performance_tracker::add_tracking_entry(const tracking_entry<cmd::parser_pr
224229
void performance_tracker::add_tracking_entry(const tracking_entry<cmd::parser_scale> &entry) {
225230
// check whether entries should currently be tracked
226231
if (this->is_tracking()) {
232+
const std::scoped_lock guard{ mutex_ };
233+
227234
// create category
228235
tracking_entries_.emplace(entry.entry_category, std::map<std::string, std::vector<std::string>>{});
229236
// fill category with value
@@ -243,8 +250,10 @@ void performance_tracker::add_tracking_entry(const tracking_entry<cmd::parser_sc
243250
#if defined(PLSSVM_HARDWARE_SAMPLING_ENABLED)
244251
void performance_tracker::add_hws_entry(const hws::system_hardware_sampler &entry) {
245252
// check whether entries should currently be tracked
246-
const std::string entry_category{ "hardware_sampler" };
247253
if (this->is_tracking()) {
254+
const std::scoped_lock guard{ mutex_ };
255+
256+
const std::string entry_category{ "hardware_sampler" };
248257
for (const std::unique_ptr<hws::hardware_sampler> &sampler : entry.samplers()) {
249258
// get the sample string and append two newlines to each line
250259
std::string sample_str = sampler->samples_only_as_yaml_string();
@@ -266,10 +275,14 @@ void performance_tracker::add_hws_entry(const hws::system_hardware_sampler &entr
266275
#endif
267276

268277
void performance_tracker::add_event(std::string name) {
278+
const std::scoped_lock guard{ mutex_ };
279+
269280
events_.add_event(std::chrono::steady_clock::now(), std::move(name));
270281
}
271282

272283
void performance_tracker::set_reference_time(const std::chrono::steady_clock::time_point time) noexcept {
284+
const std::scoped_lock guard{ mutex_ };
285+
273286
reference_time_ = time;
274287
}
275288

@@ -493,17 +506,29 @@ void performance_tracker::save(std::ostream &out) {
493506
}
494507
}
495508

496-
void performance_tracker::pause_tracking() noexcept { is_tracking_ = false; }
509+
void performance_tracker::pause_tracking() noexcept {
510+
const std::scoped_lock guard{ mutex_ };
511+
512+
is_tracking_ = false;
513+
}
497514

498-
void performance_tracker::resume_tracking() noexcept { is_tracking_ = true; }
515+
void performance_tracker::resume_tracking() noexcept {
516+
const std::scoped_lock guard{ mutex_ };
517+
518+
is_tracking_ = true;
519+
}
499520

500521
bool performance_tracker::is_tracking() const noexcept { return is_tracking_; }
501522

502523
const std::map<std::string, std::map<std::string, std::vector<std::string>>> &performance_tracker::get_tracking_entries() const noexcept { return tracking_entries_; }
503524

504525
const events &performance_tracker::get_events() const noexcept { return events_; }
505526

506-
void performance_tracker::clear_tracking_entries() noexcept { tracking_entries_.clear(); }
527+
void performance_tracker::clear_tracking_entries() noexcept {
528+
const std::scoped_lock guard{ mutex_ };
529+
530+
tracking_entries_.clear();
531+
}
507532

508533
performance_tracker &global_performance_tracker() {
509534
static performance_tracker tracker;

tests/detail/tracking/performance_tracker.cpp

Lines changed: 0 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,10 @@
2929
#include <algorithm> // std::transform
3030
#include <array> // std::array
3131
#include <chrono> // std::chrono_literals namespace
32-
#include <cstddef> // std::size_t
3332
#include <filesystem> // std::filesystem::is_empty
3433
#include <iostream> // std::cout, std::clog
3534
#include <map> // std::map
3635
#include <string> // std::string
37-
#include <utility> // std::move
3836
#include <vector> // std::vector
3937

4038
using namespace plssvm::detail::literals; // NOLINT(google-build-using-namespace): only imports custom user-defined literals into this namespace
@@ -111,132 +109,6 @@ class PerformanceTracker : public ::testing::Test,
111109
plssvm::detail::tracking::performance_tracker tracker_;
112110
};
113111

114-
TEST_F(PerformanceTracker, CopyConstruct) {
115-
// get performance tracker from fixture class
116-
plssvm::detail::tracking::performance_tracker &tracker = this->get_performance_tracker();
117-
118-
// add different tracking entries
119-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "foo", "bar", 42 });
120-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "foo", "baz", 3.1415 });
121-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "foo", "mem", 1_KiB });
122-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "", "foobar", 'a' });
123-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "", "foobar", 'b' });
124-
125-
// copy-construct new performance tracker
126-
const plssvm::detail::tracking::performance_tracker tracker2{ tracker };
127-
128-
// check the contents
129-
EXPECT_EQ(tracker2.get_tracking_entries(), tracker.get_tracking_entries());
130-
ASSERT_EQ(tracker2.get_events().num_events(), tracker.get_events().num_events());
131-
for (std::size_t i = 0; i < tracker.get_events().num_events(); ++i) {
132-
EXPECT_EQ(tracker2.get_events()[i].time_point, tracker.get_events()[i].time_point);
133-
EXPECT_EQ(tracker2.get_events()[i].name, tracker.get_events()[i].name);
134-
}
135-
EXPECT_EQ(tracker2.get_reference_time(), tracker.get_reference_time());
136-
EXPECT_EQ(tracker2.is_tracking(), tracker.is_tracking());
137-
}
138-
139-
TEST_F(PerformanceTracker, MoveConstruct) {
140-
// get performance tracker from fixture class
141-
plssvm::detail::tracking::performance_tracker &tracker = this->get_performance_tracker();
142-
143-
// add different tracking entries
144-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "foo", "bar", 42 });
145-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "foo", "baz", 3.1415 });
146-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "foo", "mem", 1_KiB });
147-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "", "foobar", 'a' });
148-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "", "foobar", 'b' });
149-
150-
// save (i.e. copy contents as ground truth)
151-
const auto entries = tracker.get_tracking_entries();
152-
const auto events = tracker.get_events();
153-
const auto reference_time = tracker.get_reference_time();
154-
const bool is_tracking = tracker.is_tracking();
155-
156-
// move-construct new performance tracker
157-
const plssvm::detail::tracking::performance_tracker tracker2{ std::move(tracker) };
158-
159-
// check the contents
160-
EXPECT_EQ(tracker2.get_tracking_entries(), entries);
161-
ASSERT_EQ(tracker2.get_events().num_events(), events.num_events());
162-
for (std::size_t i = 0; i < events.num_events(); ++i) {
163-
EXPECT_EQ(tracker2.get_events()[i].time_point, events[i].time_point);
164-
EXPECT_EQ(tracker2.get_events()[i].name, events[i].name);
165-
}
166-
EXPECT_EQ(tracker2.get_reference_time(), reference_time);
167-
EXPECT_EQ(tracker2.is_tracking(), is_tracking);
168-
169-
// check moved-from state
170-
// NOLINTBEGIN: use after move wanted since this is tested here
171-
EXPECT_TRUE(tracker.get_tracking_entries().empty());
172-
EXPECT_TRUE(tracker.get_events().empty());
173-
// NOLINTEND
174-
}
175-
176-
TEST_F(PerformanceTracker, CopyAssign) {
177-
// get performance tracker from fixture class
178-
plssvm::detail::tracking::performance_tracker &tracker = this->get_performance_tracker();
179-
180-
// add different tracking entries
181-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "foo", "bar", 42 });
182-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "foo", "baz", 3.1415 });
183-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "foo", "mem", 1_KiB });
184-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "", "foobar", 'a' });
185-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "", "foobar", 'b' });
186-
187-
// default-construct new performance tracker then copy-assign another performance tracker
188-
plssvm::detail::tracking::performance_tracker tracker2{};
189-
tracker2 = tracker;
190-
191-
// check the contents
192-
EXPECT_EQ(tracker2.get_tracking_entries(), tracker.get_tracking_entries());
193-
ASSERT_EQ(tracker2.get_events().num_events(), tracker.get_events().num_events());
194-
for (std::size_t i = 0; i < tracker.get_events().num_events(); ++i) {
195-
EXPECT_EQ(tracker2.get_events()[i].time_point, tracker.get_events()[i].time_point);
196-
EXPECT_EQ(tracker2.get_events()[i].name, tracker.get_events()[i].name);
197-
}
198-
EXPECT_EQ(tracker2.get_reference_time(), tracker.get_reference_time());
199-
EXPECT_EQ(tracker2.is_tracking(), tracker.is_tracking());
200-
}
201-
202-
TEST_F(PerformanceTracker, MoveAssign) {
203-
// get performance tracker from fixture class
204-
plssvm::detail::tracking::performance_tracker &tracker = this->get_performance_tracker();
205-
206-
// add different tracking entries
207-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "foo", "bar", 42 });
208-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "foo", "baz", 3.1415 });
209-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "foo", "mem", 1_KiB });
210-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "", "foobar", 'a' });
211-
tracker.add_tracking_entry(plssvm::detail::tracking::tracking_entry{ "", "foobar", 'b' });
212-
213-
// save (i.e. copy contents as ground truth)
214-
const auto entries = tracker.get_tracking_entries();
215-
const auto events = tracker.get_events();
216-
const auto reference_time = tracker.get_reference_time();
217-
const bool is_tracking = tracker.is_tracking();
218-
219-
// default-construct new performance tracker then move-assign another performance tracker
220-
plssvm::detail::tracking::performance_tracker tracker2{};
221-
tracker2 = std::move(tracker);
222-
223-
// check the contents
224-
EXPECT_EQ(tracker2.get_tracking_entries(), entries);
225-
ASSERT_EQ(tracker2.get_events().num_events(), events.num_events());
226-
for (std::size_t i = 0; i < events.num_events(); ++i) {
227-
EXPECT_EQ(tracker2.get_events()[i].time_point, events[i].time_point);
228-
EXPECT_EQ(tracker2.get_events()[i].name, events[i].name);
229-
}
230-
EXPECT_EQ(tracker2.get_reference_time(), reference_time);
231-
EXPECT_EQ(tracker2.is_tracking(), is_tracking);
232-
233-
// check moved-from state
234-
// NOLINTBEGIN: use after move wanted since this is tested here
235-
EXPECT_TRUE(tracker.get_tracking_entries().empty());
236-
EXPECT_TRUE(tracker.get_events().empty());
237-
// NOLINTEND
238-
}
239-
240112
// the macros are only available if PLSSVM_PERFORMANCE_TRACKER_ENABLED is defined!
241113
#if defined(PLSSVM_PERFORMANCE_TRACKER_ENABLED)
242114

0 commit comments

Comments
 (0)