DiSMEC++
collection.cpp
Go to the documentation of this file.
1 // Copyright (c) 2021, Aalto University, developed by Erik Schultheis
2 // All rights reserved.
3 //
4 // SPDX-License-Identifier: MIT
5 
6 #include "collection.h"
7 #include "spdlog/spdlog.h"
8 #include "utils/conversion.h"
9 
10 using namespace dismec::stats;
11 
13  if(!m_Statistics.at(stat.to_index()))
14  throw std::logic_error("Cannot enable tracking of id, because no `Statistics` object has been assigned.");
15  m_Enabled.at(stat.to_index()) = true;
16 }
17 
19  m_Enabled.at(stat.to_index()) = false;
20 }
21 
22 namespace {
23  stat_id_t str_to_id(const std::string& str, const std::vector<StatisticMetaData>& names) {
24  auto dist = std::distance(begin(names), std::find_if(begin(names), end(names), [&](auto&& v){ return v.Name == str; }));
25  if(dist < 0 || dist >= dismec::ssize(names)) {
26  throw std::invalid_argument("No statistics of the given name has been declared.");
27  }
28  return stat_id_t{dist};
29  }
30 }
31 
32 void StatisticsCollection::enable(const std::string& stat) {
33  enable(str_to_id(stat, m_MetaData));
34 }
35 
36 void StatisticsCollection::disable(const std::string& stat) {
38 }
39 
41  if(index.to_index() < ssize(m_Enabled)) {
42  throw std::invalid_argument("A stat with the given id already exists");
43  }
44  if(index.to_index() != ssize(m_Enabled)) {
45  throw std::invalid_argument("Currently, stats must be declared consecutively!");
46  }
47 
48  for(const auto& old : m_MetaData) {
49  if(meta.Name == old.Name) {
50  throw std::invalid_argument("A stat with the given name already exists");
51  }
52  }
53 
54  m_Enabled.push_back(false);
55  m_MetaData.emplace_back(std::move(meta));
56  m_Statistics.emplace_back();
57 }
58 
59 void StatisticsCollection::register_stat(const std::string& name, std::unique_ptr<Statistics> stat) {
60  auto id = str_to_id(name, m_MetaData);
61  if(m_Statistics.at(id.to_index()) && stat) {
62  throw std::invalid_argument("Cannot register stat. Already registered!");
63  }
64  if(stat) {
65  stat->setup(*this);
66  }
67  m_Statistics.at(id.to_index()) = std::move(stat);
68  if(m_Statistics[id.to_index()]) {
69  enable(id);
70  } else {
71  disable(id);
72  }
73 }
74 
75 const Statistics& StatisticsCollection::get_stat(const std::string& name) const {
76  auto id = str_to_id(name, m_MetaData);
77  auto* ptr = m_Statistics.at(id.to_index()).get();
78  if(!ptr)
79  throw std::invalid_argument("No Statistics registered for the given name");
80  return *ptr;
81 }
82 
83 bool StatisticsCollection::is_enabled_by_name(const std::string& name) const {
84  return is_enabled(str_to_id(name, m_MetaData));
85 }
86 
87 void StatisticsCollection::declare_tag(tag_id_t index, std::string name) {
88  if(index.to_index() < ssize(m_TagValues)) {
89  throw std::invalid_argument("A tag with the given id already exists");
90  }
91  if(index.to_index() != ssize(m_TagValues)) {
92  throw std::invalid_argument("Currently, tags must be declared consecutively!");
93  }
94 
95  for(const auto& old : m_TagValues) {
96  if(old.get_name() == name) {
97  throw std::invalid_argument("A tag with the given name already exists");
98  }
99  }
100 
101  m_TagValues.emplace_back(TagContainer::create_full_container(std::move(name)));
102  m_TagLookup.emplace(m_TagValues.back().get_name(), m_TagValues.back());
103 }
104 
105 TagContainer StatisticsCollection::get_tag_by_name(const std::string& name) const {
106  return m_TagLookup.at(name);
107 }
108 
110  for(const auto& other_tag : other.m_TagLookup) {
111  m_TagLookup.insert(other_tag);
112  // we cannot check this because of mutual registration.
113  // TODO maybe we can verify that they point to the same memory
114  /*if(!result.second) {
115  spdlog::error("Cannot combine statistics collections because tag {} is a duplicate",
116  other_tag.second.get_name());
117  throw std::runtime_error("Duplicate tag names are forbidden");
118  }*/
119  }
120 }
121 
122 bool StatisticsCollection::has_stat(const std::string& name) const {
123  return std::any_of(begin(m_MetaData), end(m_MetaData), [&](auto&& v){ return v.Name == name; });
124 }
125 
126 #include "doctest.h"
127 #include "nlohmann/json.hpp"
128 
129 // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers)
130 namespace {
131  struct MockStat : public Statistics {
132  [[nodiscard]] std::unique_ptr<Statistics> clone() const override {
133  assert(0);
134  __builtin_unreachable();
135  }
136  void merge(const Statistics& other) override {
137  assert(0);
138  __builtin_unreachable();
139  };
140 
141  [[nodiscard]] nlohmann::json to_json() const override {
142  assert(0);
143  __builtin_unreachable();
144  };
145 
146  void record_int(long integer) override { LastValue = integer; }
147 
148  long LastValue;
149  };
150 }
151 
152 TEST_CASE("check errors for stats") {
153  StatisticsCollection collection;
154  collection.declare_stat(stat_id_t{0}, {"stat"});
155  REQUIRE(collection.get_statistics_meta().size() == 1);
156 
157  // duplicate id
158  DOCTEST_REQUIRE_THROWS(collection.declare_stat(stat_id_t{0}, {"other"}));
159  REQUIRE(collection.get_statistics_meta().size() == 1);
160 
161  // duplicate name
162  DOCTEST_REQUIRE_THROWS(collection.declare_stat(stat_id_t{1}, {"stat"}));
163  REQUIRE(collection.get_statistics_meta().size() == 1);
164 
165  // enable or access without Statistics object
166  DOCTEST_REQUIRE_THROWS(collection.enable(stat_id_t{0}));
167  DOCTEST_REQUIRE_THROWS(collection.get_stat("stat"));
168 
169  // access undeclared name
170  DOCTEST_REQUIRE_THROWS(collection.enable("unknown"));
171  DOCTEST_REQUIRE_THROWS(collection.disable("unknown"));
172  DOCTEST_REQUIRE_THROWS(collection.is_enabled_by_name("unknown"));
173  DOCTEST_REQUIRE_THROWS(collection.register_stat("unknown", nullptr));
174  DOCTEST_REQUIRE_THROWS(collection.get_stat("unknown"));
175 }
176 
177 TEST_CASE("check errors for tags") {
178  StatisticsCollection collection;
179  collection.declare_tag(tag_id_t {0}, "tag");
180  REQUIRE(collection.get_all_tags().size() == 1);
181 
182  // duplicate id
183  DOCTEST_REQUIRE_THROWS(collection.declare_tag(tag_id_t{0}, "other"));
184  REQUIRE(collection.get_all_tags().size() == 1);
185 
186  // duplicate name
187  DOCTEST_REQUIRE_THROWS(collection.declare_tag(tag_id_t{1}, "tag"));
188  REQUIRE(collection.get_all_tags().size() == 1);
189 
190  // access undeclared name
191  DOCTEST_REQUIRE_THROWS(collection.get_tag_by_name("unknown"));
192 }
193 
194 TEST_CASE("register stat") {
195  StatisticsCollection collection;
196  collection.declare_stat(stat_id_t{0}, {"stat"});
197 
198  CHECK(collection.has_stat("stat"));
199  CHECK_FALSE(collection.has_stat("stat2"));
200 
201  auto stat = std::make_unique<MockStat>();
202  collection.register_stat("stat", std::move(stat));
203  CHECK(collection.is_enabled_by_name("stat"));
204 
205  DOCTEST_REQUIRE_THROWS(collection.register_stat("stat", std::make_unique<MockStat>()));
206  collection.register_stat("stat", nullptr);
207  CHECK_FALSE(collection.is_enabled_by_name("stat"));
208 
209  // now that we've reset it, we can safely set it again
210  auto second = std::make_unique<MockStat>();
211  auto* ptr = second.get();
212  collection.register_stat("stat", std::move(second));
213  CHECK(collection.is_enabled_by_name("stat"));
214 
215  CHECK(ptr == &collection.get_stat("stat"));
216 }
217 
218 TEST_CASE("enable-disable") {
219  StatisticsCollection collection;
220  collection.declare_stat(stat_id_t{0}, {"stat"});
221  collection.declare_stat(stat_id_t{1}, {"stat2"});
222 
223  collection.register_stat("stat", std::make_unique<MockStat>());
224  REQUIRE(collection.is_enabled_by_name("stat"));
225 
226  // enable and disable using both named and indexed calls
227  CHECK(collection.is_enabled(stat_id_t{0}));
228  CHECK_FALSE(collection.is_enabled(stat_id_t{1}));
229 
230  collection.disable("stat");
231  CHECK_FALSE(collection.is_enabled(stat_id_t{0}));
232  CHECK_FALSE(collection.is_enabled(stat_id_t{1}));
233 
234  collection.enable("stat");
235  CHECK(collection.is_enabled(stat_id_t{0}));
236  CHECK_FALSE(collection.is_enabled(stat_id_t{1}));
237 
238  collection.disable(stat_id_t{0});
239  CHECK_FALSE(collection.is_enabled(stat_id_t{0}));
240  CHECK_FALSE(collection.is_enabled(stat_id_t{1}));
241 
242  collection.enable(stat_id_t{0});
243  CHECK(collection.is_enabled(stat_id_t{0}));
244  CHECK_FALSE(collection.is_enabled(stat_id_t{1}));
245 }
246 
247 TEST_CASE("recording") {
248  StatisticsCollection collection;
249  collection.declare_stat(stat_id_t{0}, {"stat"});
250  collection.register_stat("stat", std::make_unique<MockStat>());
251  const auto& stat = dynamic_cast<const MockStat&>(collection.get_stat("stat"));
252 
253  // direct recording
254  collection.record(stat_id_t{0}, 5);
255  CHECK(stat.LastValue == 5);
256 
257  // callable recording
258  collection.record(stat_id_t{0}, [](){ return 8; });
259  CHECK(stat.LastValue == 8);
260 
261  // don't call the callable for a disabled stat
262  collection.disable(stat_id_t{0});
263  collection.record(stat_id_t{0}, [](){ DOCTEST_FAIL("callable was called for disabled stat"); return 0; });
264 }
265 
266 TEST_CASE("tag handling") {
267  StatisticsCollection collection;
268  collection.declare_tag(tag_id_t {0}, "tag");
269 
270  collection.set_tag(tag_id_t{0}, 25);
271 
272  CHECK(collection.get_tag_by_name("tag").get_value() == 25);
273 
274  // check that the container's value is really tied to the internal value
275  TagContainer value = collection.get_tag_by_name("tag");
276  CHECK(value.get_name() == "tag");
277 
278  collection.set_tag(tag_id_t{0}, 35);
279  CHECK(value.get_value() == 35);
280 }
281 
282 TEST_CASE("tag sharing") {
283  StatisticsCollection collection;
284  collection.declare_tag(tag_id_t {0}, "tag");
285 
286  StatisticsCollection other;
287  other.declare_tag(tag_id_t {0}, "tag2");
288 
289  collection.provide_tags(other);
290 
291  // make sure that the system does not confuse the two
292  other.set_tag(tag_id_t{0}, 25);
293  collection.set_tag(tag_id_t{0}, 15);
294 
296  auto foreign_tag = collection.get_tag_by_name("tag2");
297 
298  CHECK(foreign_tag.get_value() == 25);
299 
300  // check dupliate errors
301  /*
302  other.declare_tag(tag_id_t{1}, "tag");
303  DOCTEST_CHECK_THROWS(collection.provide_tags(other));
304  */
305 }
306 // NOLINTEND(cppcoreguidelines-avoid-magic-numbers)
constexpr T to_index() const
! Explicitly convert to an integer.
Definition: opaque_int.h:32
This class manages a collection of named Statistics objects.
Definition: collection.h:47
void declare_tag(tag_id_t index, std::string name)
Declares a new tag value.
Definition: collection.cpp:87
std::vector< StatisticMetaData > m_MetaData
Definition: collection.h:225
TagContainer get_tag_by_name(const std::string &name) const
Gets the tag with the given name.
Definition: collection.cpp:105
bool is_enabled(stat_id_t stat) const
Quickly checks whether collection of data is enabled for the given statistics.
Definition: collection.h:131
std::vector< std::unique_ptr< Statistics > > m_Statistics
Definition: collection.h:226
const Statistics & get_stat(const std::string &name) const
Returns the Statistics object corresponding to the slot name.
Definition: collection.cpp:75
void provide_tags(const StatisticsCollection &other)
Registers all the tags of the other collection as read-only tags in this collection.
Definition: collection.cpp:109
void disable(stat_id_t stat)
Explicitly disable the collection of statistics for the given index.
Definition: collection.cpp:18
const std::vector< StatisticMetaData > & get_statistics_meta() const
Gets a vector with the declarations for all statistics.
Definition: collection.h:82
const std::vector< TagContainer > & get_all_tags() const
Gets a vector which contains all the tags owned by this collection.
Definition: collection.h:101
bool is_enabled_by_name(const std::string &name) const
Checks whether gathering data is enabled based on the statistic's name.
Definition: collection.cpp:83
void declare_stat(stat_id_t index, StatisticMetaData meta)
Declares a new statistics. Defines the corresponding index and name.
Definition: collection.cpp:40
std::vector< TagContainer > m_TagValues
Definition: collection.h:228
void enable(stat_id_t stat)
Explicitly enable the collection of statistics for the given index.
Definition: collection.cpp:12
bool has_stat(const std::string &name) const
Returns whether a stat with the given name is declared.
Definition: collection.cpp:122
void set_tag(tag_id_t tag, int value)
Sets the tag to the given integer value.
Definition: collection.h:209
void record(stat_id_t stat, T &&value)
Records an already computed value.
Definition: collection.h:174
void register_stat(const std::string &name, std::unique_ptr< Statistics > stat)
Registers a Statistics object to the named slot.
Definition: collection.cpp:59
std::unordered_map< std::string, TagContainer > m_TagLookup
Definition: collection.h:229
TODO maybe we should solve this with a variant which does the dispatch of expected type and tag.
Definition: stats_base.h:65
A tag container combines a name with a shared pointer, which points to the tag value.
Definition: stats_base.h:30
static TagContainer create_full_container(std::string name)
Definition: stats_base.h:53
int get_value() const
Returns the current value of the tag. Requires the container to not be empty.
Definition: stats_base.h:36
const std::string & get_name() const
returns the name of the associated tag
Definition: stats_base.h:33
TEST_CASE("check errors for stats")
Definition: collection.cpp:152
nlohmann::json json
Definition: model-io.cpp:22
stat_id_t str_to_id(const std::string &str, const std::vector< StatisticMetaData > &names)
Definition: collection.cpp:23
constexpr auto ssize(const C &c) -> std::common_type_t< std::ptrdiff_t, std::make_signed_t< decltype(c.size())>>
signed size free function. Taken from https://en.cppreference.com/w/cpp/iterator/size
Definition: conversion.h:42
void merge(const Statistics &other) override
Merges this statistics of another one of the same type and settings.
Definition: collection.cpp:136
void record_int(long integer) override
Definition: collection.cpp:146
nlohmann::json to_json() const override
Converts the statistics current value into a json object.
Definition: collection.cpp:141
std::unique_ptr< Statistics > clone() const override
Definition: collection.cpp:132
Data that is associated with each declared statistics.
Definition: stat_id.h:33
std::string Name
The name of the stat. This is how it will be identified.
Definition: stat_id.h:34