diff --git a/.clang-tidy b/.clang-tidy index 8fda9cc..6022c4b 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -22,7 +22,7 @@ WarningsAsErrors: '*' CheckOptions: - key: readability-identifier-naming.NamespaceCase - value: 'CamelCase' + value: 'camelBack' - key: readability-identifier-naming.StructCase value: 'CamelCase' - key: readability-identifier-naming.FunctionCase @@ -45,6 +45,8 @@ CheckOptions: value: 'camelBack' - key: readability-identifier-naming.PrivateMemberPrefix value: 'm_' + - key: readability-identifier-naming.ProtectedMemberPrefix + value: 'm_' - key: readability-identifier-naming.ConstantMemberCase value: 'UPPER_CASE' - key: readability-identifier-naming.EnumConstantCase diff --git a/Makefile b/Makefile index 8afd119..86bc606 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,8 @@ ifeq ($(RUN_CLANG_TIDY),) RUN_CLANG_TIDY := run-clang-tidy endif -SOURCE_DIR = src/ include/ +HEADE_FILTER = "$(shell pwd)/src|$(shell pwd)/include" +SOURCE_DIR = "$(shell pwd)/src" "$(shell pwd)/include" SOURCE_REGEX = '.*\.\(cpp\|hpp\)' .PHONY: all @@ -40,11 +41,11 @@ format-fix: .PHONY: tidy tidy: all - $(RUN_CLANG_TIDY) -p build -quiet -j $(shell nproc) $(SOURCE_DIR) + $(RUN_CLANG_TIDY) -p build -quiet -j $(shell nproc) -header-filter=$(HEADE_FILTER) $(SOURCE_DIR) .PHONY: tidy-fix tidy-fix: all - $(RUN_CLANG_TIDY) -p build -quiet -fix -j $(shell nproc) $(SOURCE_DIR) + $(RUN_CLANG_TIDY) -p build -quiet -fix -j $(shell nproc) -header-filter=$(HEADE_FILTER) $(SOURCE_DIR) .PHONY: test test: build diff --git a/include/telemetry/aggFile.hpp b/include/telemetry/aggFile.hpp index daeff6c..c0b986b 100644 --- a/include/telemetry/aggFile.hpp +++ b/include/telemetry/aggFile.hpp @@ -61,7 +61,7 @@ class AggregatedFile : public File { FileOps getOps(); - const std::string m_filesRegexPattern; + const std::string M_FILES_REGEX_PATTERN; std::shared_ptr m_patternRootDir; std::vector m_paths; diff --git a/include/telemetry/aggMethod.hpp b/include/telemetry/aggMethod.hpp index 1368059..2fa5ad1 100644 --- a/include/telemetry/aggMethod.hpp +++ b/include/telemetry/aggMethod.hpp @@ -36,7 +36,9 @@ enum class AggMethodType { AVG, SUM, JOIN }; */ struct AggOperation { AggMethodType method; ///< Aggregation method + // NOLINTNEXTLINE(readability-redundant-string-init) std::string dictFieldName = ""; ///< Name of the field in the dictionary + // NOLINTNEXTLINE(readability-redundant-string-init) std::string dictResultName = ""; ///< Name of the field in the aggregated dictionary }; @@ -78,6 +80,9 @@ class AggMethod { protected: AggContent getAggContent(const Content& content, bool useDictResultName = false); + [[nodiscard]] std::string getDictResultName() const { return m_dictResultname; } + +private: std::string m_dictFieldName; std::string m_dictResultname; }; diff --git a/include/telemetry/node.hpp b/include/telemetry/node.hpp index e3fb096..e491bd9 100644 --- a/include/telemetry/node.hpp +++ b/include/telemetry/node.hpp @@ -64,9 +64,11 @@ class Node : public std::enable_shared_from_this { std::string getFullPath(); protected: - std::shared_ptr m_parent; + std::shared_ptr getParent() { return m_parent; }; private: + std::shared_ptr m_parent; + std::mutex m_mutex; std::string m_name; diff --git a/src/telemetry/aggFile.cpp b/src/telemetry/aggFile.cpp index 481dcdd..e62a5e1 100644 --- a/src/telemetry/aggFile.cpp +++ b/src/telemetry/aggFile.cpp @@ -119,10 +119,10 @@ Content AggregatedFile::read() if (m_patternRootDir) { patternRootDir = m_patternRootDir; } else { - patternRootDir = std::dynamic_pointer_cast(m_parent); + patternRootDir = std::dynamic_pointer_cast(getParent()); } - const auto files = getFilesMatchingPattern(m_filesRegexPattern, patternRootDir); + const auto files = getFilesMatchingPattern(M_FILES_REGEX_PATTERN, patternRootDir); if (files.empty()) { return content; } @@ -159,7 +159,7 @@ AggregatedFile::AggregatedFile( const std::vector& ops, std::shared_ptr patternRootDir) : File(parent, name, getOps()) - , m_filesRegexPattern(std::move(aggFilesPattern)) + , M_FILES_REGEX_PATTERN(std::move(aggFilesPattern)) , m_patternRootDir(std::move(patternRootDir)) { validateAggOperations(ops); diff --git a/src/telemetry/aggregator/aggCommon.hpp b/src/telemetry/aggregator/aggCommon.hpp index 8e61567..cb52238 100644 --- a/src/telemetry/aggregator/aggCommon.hpp +++ b/src/telemetry/aggregator/aggCommon.hpp @@ -69,7 +69,7 @@ static ScalarWithUnit getReferenceVariant(const std::vector& values) if (std::holds_alternative(values.front())) { for (const auto& value : values) { - const Array& array = std::get(value); + const auto& array = std::get(value); if (!array.empty()) { return {array.front(), ""}; } @@ -102,7 +102,7 @@ static bool containsSameScalarAlternative(const std::vector& values) return false; } - size_t refIndex = refScalar.index(); + const size_t refIndex = refScalar.index(); for (const auto& value : values) { if (std::holds_alternative(value)) { @@ -119,7 +119,7 @@ static bool containsSameScalarAlternative(const std::vector& values) return false; } } else if (std::holds_alternative(value)) { - const Array& array = std::get(value); + const auto& array = std::get(value); if (std::any_of(array.begin(), array.end(), [&](const auto& scalar) { return scalar.index() != refIndex; })) { diff --git a/src/telemetry/aggregator/aggJoin.cpp b/src/telemetry/aggregator/aggJoin.cpp index 0e9e2d5..408493b 100644 --- a/src/telemetry/aggregator/aggJoin.cpp +++ b/src/telemetry/aggregator/aggJoin.cpp @@ -61,7 +61,7 @@ Content AggMethodJoin::aggregate(const std::vector& contents) } const auto& result = aggregateGatheredValues(values); - return createContent(m_dictResultname, result); + return createContent(getDictResultName(), result); } } // namespace telemetry diff --git a/src/telemetry/aggregator/aggMinMax.cpp b/src/telemetry/aggregator/aggMinMax.cpp new file mode 100644 index 0000000..e6bc19e --- /dev/null +++ b/src/telemetry/aggregator/aggMinMax.cpp @@ -0,0 +1,183 @@ +/** + * @file + * @author Pavel Siska + * @brief Implementaion of the MIN aggregation method for telemetry data. + * + * @note SPDX-License-Identifier: BSD-3-Clause + */ + +#include "aggMinMax.hpp" + +#include "aggCommon.hpp" + +#include + +namespace telemetry { + +using ResultType = std::variant; + +static void findMin(const Scalar& value, Scalar& result) +{ + if (std::holds_alternative(result)) { + result = value; + return; + } + + if (std::holds_alternative(value)) { + if (std::get(value) < std::get(result)) { + result = value; + } + } else if (std::holds_alternative(value)) { + if (std::get(value) < std::get(result)) { + result = value; + } + } else if (std::holds_alternative(value)) { + if (std::get(value) < std::get(result)) { + result = value; + } + } else { + throw TelemetryException("Invalid scalar alternative type for min operation."); + } +} + +static void findMax(const Scalar& value, Scalar& result) +{ + if (std::holds_alternative(result)) { + result = value; + return; + } + + if (std::holds_alternative(value)) { + if (std::get(value) > std::get(result)) { + result = value; + } + } else if (std::holds_alternative(value)) { + if (std::get(value) > std::get(result)) { + result = value; + } + } else if (std::holds_alternative(value)) { + if (std::get(value) > std::get(result)) { + result = value; + } + } else { + throw TelemetryException("Invalid scalar alternative type for max operation."); + } +} + +static Scalar +aggregateScalar(std::vector& values, const AggMethodMinMax::AggMethod& aggMethod) +{ + Scalar result = std::monostate(); + + if (values.empty()) { + return result; + } + + if (!std::holds_alternative(values.front())) { + throw TelemetryException("Unexpected variant alternative."); + } + + for (const auto& value : values) { + const auto& scalar = std::get(value); + aggMethod(scalar, result); + } + + return result; +} + +static ScalarWithUnit aggregateScalarWithUnit( + std::vector& values, + const AggMethodMinMax::AggMethod& aggMethod) +{ + Scalar result = std::monostate(); + + if (values.empty()) { + return {}; + } + + if (!std::holds_alternative(values.front())) { + throw TelemetryException("Unexpected variant alternative."); + } + + for (const auto& value : values) { + [[maybe_unused]] const auto& [scalar, _] = std::get(value); + aggMethod(scalar, result); + } + + [[maybe_unused]] const auto& [_, unit] = std::get(values.front()); + + return {result, unit}; +} + +static ResultType aggregateGatheredValues( + std::vector& values, + const AggMethodMinMax::AggMethod& aggMethod) +{ + if (std::holds_alternative(values.front())) { + return aggregateScalar(values, aggMethod); + } + + if (std::holds_alternative(values.front())) { + return aggregateScalarWithUnit(values, aggMethod); + } + + throw TelemetryException("Unexpected variant alternative."); +} + +static Content createDictContent(const std::string& dictKey, const ResultType& result) +{ + Dict dict; + + auto visitor = [&](const auto& arg) -> DictValue { return arg; }; + dict[dictKey] = std::visit(visitor, result); + + return dict; +} + +static Content createContent(const std::string& dictKey, const ResultType& result) +{ + if (!dictKey.empty()) { + return createDictContent(dictKey, result); + } + + auto visitor = [&](const auto& arg) -> Content { return arg; }; + return std::visit(visitor, result); +} + +AggMethodMinMax::AggMethodMinMax(const AggMethodType& method) +{ + if (method == AggMethodType::MIN) { + m_agregateFunction = findMin; + } else if (method == AggMethodType::MAX) { + m_agregateFunction = findMax; + } else { + throw TelemetryException("Invalid aggregation method."); + } +} + +Content AggMethodMinMax::aggregate(const std::vector& contents) +{ + std::vector values; + + for (const auto& content : contents) { + const auto& aggContent = getAggContent(content); + values.emplace_back(aggContent); + } + + if (!hasOneOfThisAlternative(values)) { + throw TelemetryException("The contents data does not contain the same variant alternative"); + } + + if (!hasValidScalarType(values)) { + throw TelemetryException("Invalid scalar variant alternative"); + } + + const auto& result = aggregateGatheredValues(values, m_agregateFunction); + return createContent(getDictResultName(), result); +} + +} // namespace telemetry + +#ifdef TELEMETRY_ENABLE_TESTS +#include "tests/testAggMinMax.cpp" +#endif diff --git a/src/telemetry/aggregator/aggMinMax.hpp b/src/telemetry/aggregator/aggMinMax.hpp new file mode 100644 index 0000000..00eff2b --- /dev/null +++ b/src/telemetry/aggregator/aggMinMax.hpp @@ -0,0 +1,42 @@ +/** + * @file + * @author Pavel Siska + * @brief Interface of the MIN aggregation method for telemetry data. + * + * @note SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include +#include + +#include +#include +#include + +namespace telemetry { + +/** + * @brief Implementation of the MIN aggregation method. + */ +class AggMethodMinMax : public AggMethod { +public: + AggMethodMinMax(const AggMethodType& method); + + /** + * @brief Aggregate telemetry data using the MIN method. + * + * @param contents The vector of telemetry content to aggregate. + * @return The aggregated content. + * @throws TelemetryException if the aggregation encounters an error. + */ + Content aggregate(const std::vector& contents) override; + + using AggMethod = std::function; + +private: + AggMethod m_agregateFunction; +}; + +} // namespace telemetry diff --git a/src/telemetry/aggregator/aggSum.cpp b/src/telemetry/aggregator/aggSum.cpp index be66ce6..8ec8c18 100644 --- a/src/telemetry/aggregator/aggSum.cpp +++ b/src/telemetry/aggregator/aggSum.cpp @@ -99,8 +99,9 @@ static Content createDictContent(const std::string& dictKey, const ResultType& r Content AggMethodSum::createContent(const ResultType& result) { - if (!m_dictResultname.empty()) { - return createDictContent(m_dictResultname, result); + const auto dictResultName = getDictResultName(); + if (!dictResultName.empty()) { + return createDictContent(dictResultName, result); } auto visitor = [&](const auto& arg) -> Content { return arg; }; diff --git a/src/telemetry/aggregator/tests/testAggMinMax.cpp b/src/telemetry/aggregator/tests/testAggMinMax.cpp new file mode 100644 index 0000000..dca29b4 --- /dev/null +++ b/src/telemetry/aggregator/tests/testAggMinMax.cpp @@ -0,0 +1,206 @@ +/** + * @file + * @author Pavel Siska + * @brief Unit tests of Telemetry::AggMethodSum + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include + +namespace telemetry { + +/** + * @test Test min of scalar values + */ +TEST(AggMinMaxTest, TestMin) +{ + // Test min of uint64_t scalar + { + Scalar result = uint64_t(0); + sumarize(uint64_t(5), result); + EXPECT_EQ(uint64_t(5), std::get(result)); + } + + // Test min of int64_t scalar + { + Scalar result = std::monostate(); + sumarize(int64_t(5), result); + EXPECT_EQ(int64_t(5), std::get(result)); + } + + // Test min of double scalar + { + Scalar result = double(0); + sumarize(double(5.0), result); + EXPECT_EQ(double(5.0), std::get(result)); + } + + // Test min of scalar types with different types (expect failure) + { + Scalar result = uint64_t(5); + EXPECT_THROW(sumarize(int64_t(5), result), std::exception); + } +} + +/** + * @test Test aggregation of scalar values + */ +TEST(AggMinMaxTest, TestAggregateScalar) +{ + // Test aggregation of scalar values + { + std::vector values = {Scalar {5.0}, Scalar {10.0}, Scalar {15.0}}; + Scalar result = aggregateScalar(values); + EXPECT_EQ(Scalar {30.0}, result); + } + + // Test aggregation of ScalarWithUnit values (expect failure) + { + std::vector values = {ScalarWithUnit {5.0, "unit"}}; + EXPECT_THROW(aggregateScalar(values), TelemetryException); + } + + // Test aggregation of empty vector + { + std::vector values = {}; + Scalar result = aggregateScalar(values); + EXPECT_TRUE(std::holds_alternative(result)); + } +} + +/** + * @test Test aggregation of scalar values with units + */ +TEST(AggMinMaxTest, TestAggregateScalarWithUnit) +{ + // Test aggregation of scalar values with unit + { + std::vector values + = {ScalarWithUnit {5.0, "unit"}, + ScalarWithUnit {10.0, "unit"}, + ScalarWithUnit {15.0, "unit"}}; + const auto& [scalar, unit] = aggregateScalarWithUnit(values); + EXPECT_EQ(std::get(scalar), 30.0); + EXPECT_EQ(unit, "unit"); + } + + // Test aggregation of Scalar values (expect failure) + { + std::vector values = {Scalar {5.0}}; + EXPECT_THROW(aggregateScalarWithUnit(values), TelemetryException); + } + + // Test aggregation of empty vector + { + std::vector values = {}; + const auto& [scalar, unit] = aggregateScalarWithUnit(values); + EXPECT_TRUE(std::holds_alternative(scalar)); + EXPECT_EQ(unit, ""); + } +} + +/** + * @test Test creation of dictionary content + */ +TEST(AggMinMaxTest, TestCreateDictContent) +{ + ResultType result = Scalar {uint64_t(30)}; + Content content = createDictContent("sum", result); + + EXPECT_TRUE(std::holds_alternative(content)); + + Dict& contentDict = std::get(content); + EXPECT_EQ(1, contentDict.size()); + + auto iter = contentDict.cbegin(); + { + const auto& [key, value] = *(iter++); + EXPECT_EQ("sum", key); + EXPECT_TRUE(std::holds_alternative(value)); + const auto& scalar = std::get(value); + EXPECT_TRUE(std::holds_alternative(scalar)); + EXPECT_EQ(uint64_t(30), std::get(scalar)); + } +} + +/** + * @test Test aggregation method for sum + */ +TEST(AggMinMaxTest, TestAggregate) +{ + // Test aggregation of scalar values + { + AggMethodSum aggMethodSum; + std::vector contents = {Scalar {5.0}, Scalar {10.0}, Scalar {15.0}}; + Content content = aggMethodSum.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + Scalar& scalar = std::get(content); + EXPECT_TRUE(std::holds_alternative(scalar)); + double result = std::get(scalar); + EXPECT_EQ(30.0, result); + } + + // Test aggregation of ScalarWithUnit values + { + AggMethodSum aggMethodSum; + std::vector contents = {ScalarWithUnit {5.0, "unit"}}; + Content content = aggMethodSum.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + const auto& [scalar, unit] = std::get(content); + EXPECT_EQ(5.0, std::get(scalar)); + EXPECT_EQ("unit", unit); + } + + // Test aggregation of mixed types (expect failure) + { + AggMethodSum aggMethodSum; + std::vector contents = {ScalarWithUnit {5.0, "unit"}, Scalar {5.0}}; + EXPECT_THROW(aggMethodSum.aggregate(contents), TelemetryException); + } + + // Test aggregation of incompatible types (expect failure) + { + AggMethodSum aggMethodSum; + std::vector contents = {Scalar {true}, Scalar {5.0}}; + EXPECT_THROW(aggMethodSum.aggregate(contents), TelemetryException); + } + + // Test aggregation of incompatible scalar types (expect failure) + { + AggMethodSum aggMethodSum; + std::vector contents = {Scalar {uint64_t(20)}, Scalar {5.0}}; + EXPECT_THROW(aggMethodSum.aggregate(contents), TelemetryException); + } + + // Test aggregation of scalar and uint64_t types + { + AggMethodSum aggMethodSum; + std::vector contents = {Scalar {uint64_t(20)}, uint64_t {5}}; + Content content = aggMethodSum.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + const auto& scalar = std::get(content); + EXPECT_EQ(25, std::get(scalar)); + } + + // Test aggregation of dictionary values + { + AggMethodSum aggMethodSum; + aggMethodSum.setDictField("packets", "packetsSum"); + std::vector contents + = {Dict({{"packets", Scalar {uint64_t(1)}}}), + Dict({{"packets", Scalar {uint64_t(5)}}})}; + Content content = aggMethodSum.aggregate(contents); + EXPECT_TRUE(std::holds_alternative(content)); + + const Dict& dict = std::get(content); + EXPECT_EQ(1, dict.size()); + + const Scalar& scalarValueSum = std::get(dict.at("packetsSum")); + EXPECT_EQ(uint64_t(6), std::get(scalarValueSum)); + } +} + +} // namespace telemetry