From 410c5e31a763b2f6d12dc1421a03ef9c0894f51c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20Domozi?= Date: Fri, 9 May 2025 15:51:42 +0200 Subject: [PATCH 1/4] Implement relational cohesion metric. --- .../model/include/model/cppcohesionmetrics.h | 10 ++++ .../model/include/model/cppfilemetrics.h | 7 +-- .../include/model/cpptypedependencymetrics.h | 14 ++++++ .../cppmetricsparser/cppmetricsparser.h | 3 ++ .../parser/src/cppmetricsparser.cpp | 49 +++++++++++++++++++ 5 files changed, 80 insertions(+), 3 deletions(-) diff --git a/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h b/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h index 2e4ce93eb..b77cca36e 100644 --- a/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h +++ b/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h @@ -25,6 +25,16 @@ struct CohesionCppRecordView CppAstNodeId astNodeId; }; +#pragma db view \ + object(CppRecord) \ + object(CppAstNode : CppRecord::astNodeId == CppAstNode::id) \ + object(File : CppAstNode::location.file) +struct CohesionCppRecord_Count +{ + #pragma db column("count(" + CppEntity::id + ")") + std::size_t count; +}; + #pragma db view \ object(CppMemberType) \ object(CppAstNode : CppMemberType::memberAstNode) \ diff --git a/plugins/cpp_metrics/model/include/model/cppfilemetrics.h b/plugins/cpp_metrics/model/include/model/cppfilemetrics.h index b5f6f24dc..6bc86d270 100644 --- a/plugins/cpp_metrics/model/include/model/cppfilemetrics.h +++ b/plugins/cpp_metrics/model/include/model/cppfilemetrics.h @@ -13,7 +13,8 @@ struct CppFileMetrics { enum Type { - EFFERENT_MODULE + EFFERENT_MODULE, + RELATIONAL_COHESION_MODULE }; #pragma db id auto @@ -26,7 +27,7 @@ struct CppFileMetrics Type type; #pragma db not_null - unsigned value; + double value; }; #pragma db view \ @@ -44,7 +45,7 @@ struct CppModuleMetricsForPathView CppFileMetrics::Type type; #pragma db column(CppFileMetrics::value) - unsigned value; + double value; }; #pragma db view \ diff --git a/plugins/cpp_metrics/model/include/model/cpptypedependencymetrics.h b/plugins/cpp_metrics/model/include/model/cpptypedependencymetrics.h index cca2370e5..f18f87aa8 100644 --- a/plugins/cpp_metrics/model/include/model/cpptypedependencymetrics.h +++ b/plugins/cpp_metrics/model/include/model/cpptypedependencymetrics.h @@ -49,6 +49,20 @@ struct CppTypeDependencyMetricsPathView std::string dependencyPath; }; +#pragma db view \ + object(CppTypeDependencyMetrics) \ + object(CppAstNode = EntityAstNode : CppTypeDependencyMetrics::entityHash == EntityAstNode::entityHash \ + && EntityAstNode::astType == cc::model::CppAstNode::AstType::Definition) \ + object(File = EntityFile : EntityAstNode::location.file == EntityFile::id) \ + object(CppAstNode = DependencyAstNode : CppTypeDependencyMetrics::dependencyHash == DependencyAstNode::entityHash \ + && DependencyAstNode::astType == cc::model::CppAstNode::AstType::Definition) \ + object(File = DependencyFile : DependencyAstNode::location.file == DependencyFile::id) +struct CppTypeDependencyMetrics_Count +{ + #pragma db column("count(" + CppTypeDependencyMetrics::id + ")") + std::size_t count; +}; + #pragma db view \ object(CppTypeDependencyMetrics) \ object(CppAstNode = EntityAstNode : CppTypeDependencyMetrics::entityHash == EntityAstNode::entityHash \ diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 4f9c21f9e..83459ac77 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -82,6 +82,8 @@ class CppMetricsParser : public AbstractParser void afferentTypeLevel(); // Calculate the efferent coupling at module level. void efferentModuleLevel(); + // Calculate relational cohesion at module level. + void relationalCohesionModuleLevel(); // Returns module path query based on parser configuration. odb::query getModulePathsQuery(); @@ -207,6 +209,7 @@ class CppMetricsParser : public AbstractParser static const int efferentCouplingTypesPartitionMultiplier = 5; static const int afferentCouplingTypesPartitionMultiplier = 5; static const int efferentCouplingModulesPartitionMultiplier = 5; + static const int relationalCohesionPartitionMultiplier = 5; }; } // parser diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 55a390147..0a56d8368 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -576,6 +576,53 @@ void CppMetricsParser::efferentModuleLevel() }); } +void CppMetricsParser::relationalCohesionModuleLevel() +{ + // Compute relational cohesion defined by CppDepend: + // https://www.cppdepend.com/documentation/code-metrics#RelationalCohesion + + parallelCalcMetric( + "Relational cohesion at module level", + _threadCount * relationalCohesionPartitionMultiplier,// number of jobs; adjust for granularity + getModulePathsQuery(), + [&, this](const MetricsTasks& tasks) + { + util::OdbTransaction{_ctx.db}([&, this] + { + typedef odb::query TypeDependencyQuery; + typedef model::CppTypeDependencyMetrics_Count TypeDependencyResult; + typedef odb::query RecordQuery; + typedef model::CohesionCppRecord_Count RecordResult; + + for (const model::File& file : tasks) + { + TypeDependencyResult dependencies = _ctx.db->query_value( + TypeDependencyQuery::EntityFile::path.like(file.path + '%') && + TypeDependencyQuery::DependencyFile::path.like(file.path + '%')); + + // Let R be the number of type relationships that are internal to a module + // (i.e that do not connect to types outside the module) + const std::size_t r = dependencies.count; + + RecordResult types = _ctx.db->query_value( + RecordQuery::File::path.like(file.path + '%')); + + // Let N be the number of types within the module. + const std::size_t n = types.count; + + // Relational cohesion + const double h = (n != 0) ? (double)(r + 1) / n : 0; + + model::CppFileMetrics metric; + metric.file = file.id; + metric.type = model::CppFileMetrics::Type::RELATIONAL_COHESION_MODULE; + metric.value = h; + _ctx.db->persist(metric); + } + }); + }); +} + bool CppMetricsParser::parse() { LOG(info) << "[cppmetricsparser] Computing function parameter count metric."; @@ -594,6 +641,8 @@ bool CppMetricsParser::parse() afferentTypeLevel(); LOG(info) << "[cppmetricsparser] Computing efferent coupling metric at module level."; efferentModuleLevel(); // This metric needs to be calculated after efferentTypeLevel + LOG(info) << "[cppmetricsparser] Computing relational cohesion metric at module level."; + relationalCohesionModuleLevel(); // This metric needs to be calculated after efferentTypeLevel return true; } From 1519c8a011baf81c309931000b4c01d44b6868a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20Domozi?= Date: Fri, 9 May 2025 17:29:23 +0200 Subject: [PATCH 2/4] Added test cases for relational cohesion metric. --- .../test/sources/parser/modulemetrics.cpp | 9 +++++ .../test/sources/parser/rc_module_a/a1.h | 15 +++++++ .../test/sources/parser/rc_module_a/a2.h | 13 +++++++ .../test/sources/parser/rc_module_a/a3.h | 9 +++++ .../test/sources/parser/rc_module_b/b1.h | 9 +++++ .../test/sources/parser/rc_module_c/c1.h | 17 ++++++++ .../test/sources/parser/rc_module_c/c2.h | 13 +++++++ .../test/sources/parser/rc_module_c/c3.h | 9 +++++ .../test/sources/parser/rc_module_c/c4.h | 9 +++++ .../test/src/cppmetricsparsertest.cpp | 39 +++++++++++++++++++ 10 files changed, 142 insertions(+) create mode 100644 plugins/cpp_metrics/test/sources/parser/rc_module_a/a1.h create mode 100644 plugins/cpp_metrics/test/sources/parser/rc_module_a/a2.h create mode 100644 plugins/cpp_metrics/test/sources/parser/rc_module_a/a3.h create mode 100644 plugins/cpp_metrics/test/sources/parser/rc_module_b/b1.h create mode 100644 plugins/cpp_metrics/test/sources/parser/rc_module_c/c1.h create mode 100644 plugins/cpp_metrics/test/sources/parser/rc_module_c/c2.h create mode 100644 plugins/cpp_metrics/test/sources/parser/rc_module_c/c3.h create mode 100644 plugins/cpp_metrics/test/sources/parser/rc_module_c/c4.h diff --git a/plugins/cpp_metrics/test/sources/parser/modulemetrics.cpp b/plugins/cpp_metrics/test/sources/parser/modulemetrics.cpp index 9c6e16c34..8b924600f 100644 --- a/plugins/cpp_metrics/test/sources/parser/modulemetrics.cpp +++ b/plugins/cpp_metrics/test/sources/parser/modulemetrics.cpp @@ -4,3 +4,12 @@ #include "./module_b/b2.h" #include "./module_c/c1.h" #include "./module_c/c2.h" + +#include "./rc_module_a/a1.h" +#include "./rc_module_a/a2.h" +#include "./rc_module_a/a3.h" +#include "./rc_module_b/b1.h" +#include "./rc_module_c/c1.h" +#include "./rc_module_c/c2.h" +#include "./rc_module_c/c3.h" +#include "./rc_module_c/c4.h" diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_a/a1.h b/plugins/cpp_metrics/test/sources/parser/rc_module_a/a1.h new file mode 100644 index 000000000..5899897e6 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_a/a1.h @@ -0,0 +1,15 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_A1 +#define CC_CPP_RC_MODULE_METRICS_TEST_A1 + +#include "./a2.h" +#include "./a3.h" + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class A1 { + A2 a2; + A3 a3; +}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_a/a2.h b/plugins/cpp_metrics/test/sources/parser/rc_module_a/a2.h new file mode 100644 index 000000000..60a21f10f --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_a/a2.h @@ -0,0 +1,13 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_A2 +#define CC_CPP_RC_MODULE_METRICS_TEST_A2 + +#include "./a3.h" + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class A2 { + A3 a3; +}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_a/a3.h b/plugins/cpp_metrics/test/sources/parser/rc_module_a/a3.h new file mode 100644 index 000000000..8365c3615 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_a/a3.h @@ -0,0 +1,9 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_A3 +#define CC_CPP_RC_MODULE_METRICS_TEST_A3 + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class A3 {}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_b/b1.h b/plugins/cpp_metrics/test/sources/parser/rc_module_b/b1.h new file mode 100644 index 000000000..e432443bc --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_b/b1.h @@ -0,0 +1,9 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_B1 +#define CC_CPP_RC_MODULE_METRICS_TEST_B1 + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class B1 {}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_c/c1.h b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c1.h new file mode 100644 index 000000000..8f7cadd5f --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c1.h @@ -0,0 +1,17 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_C1 +#define CC_CPP_RC_MODULE_METRICS_TEST_C1 + +#include "../rc_module_a/a1.h" +#include "../rc_module_a/a2.h" +#include "../rc_module_a/a3.h" + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class C1 { + A1 a1; + A2 a2; + A3 a3; +}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_c/c2.h b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c2.h new file mode 100644 index 000000000..bb99cb474 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c2.h @@ -0,0 +1,13 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_C2 +#define CC_CPP_RC_MODULE_METRICS_TEST_C2 + +#include "./c1.h" + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class C2 { + C1 c1; +}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_c/c3.h b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c3.h new file mode 100644 index 000000000..4412a9913 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c3.h @@ -0,0 +1,9 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_C3 +#define CC_CPP_RC_MODULE_METRICS_TEST_C3 + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class C3 {}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_c/c4.h b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c4.h new file mode 100644 index 000000000..5e0467aa9 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c4.h @@ -0,0 +1,9 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_C4 +#define CC_CPP_RC_MODULE_METRICS_TEST_C4 + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class C4 {}; +} + +#endif diff --git a/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp b/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp index 3ab40e57b..15aae8f03 100644 --- a/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp +++ b/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp @@ -24,6 +24,7 @@ using namespace cc; extern char* dbConnectionString; typedef std::pair StringUintParam; +typedef std::pair StringDoubleParam; class CppMetricsParserTest : public ::testing::Test { @@ -353,3 +354,41 @@ INSTANTIATE_TEST_SUITE_P( ParameterizedEfferentModuleCouplingTest, ::testing::ValuesIn(paramEfferentModule) ); + +// Relational cohesion at module level + +class ParameterizedRelationalCohesionTest + : public CppMetricsParserTest, + public ::testing::WithParamInterface +{}; + +// Relational cohesion formula: +// H = (R + 1)/ N +// where R is the number of type relationships that are internal to a module, +// N is the number of types within a module. + +std::vector paramRelationalCohesion = { + {"%/test/sources/parser/rc_module_a", (3 + 1) / 3.0}, + {"%/test/sources/parser/rc_module_b", (0 + 1) / 1.0}, + {"%/test/sources/parser/rc_module_c", (1 + 1) / 4.0}, +}; + +TEST_P(ParameterizedRelationalCohesionTest, RelationalCohesionTest) { + _transaction([&, this]() { + + typedef odb::query CppModuleMetricsQuery; + + const auto metric = _db->query_value( + CppModuleMetricsQuery::CppFileMetrics::type == model::CppFileMetrics::Type::RELATIONAL_COHESION_MODULE && + CppModuleMetricsQuery::File::path.like(GetParam().first)); + + constexpr double tolerance = 1e-8; + EXPECT_NEAR(GetParam().second, metric.value, tolerance); + }); +} + +INSTANTIATE_TEST_SUITE_P( + ParameterizedRelationalCohesionTestSuite, + ParameterizedRelationalCohesionTest, + ::testing::ValuesIn(paramRelationalCohesion) +); From 3c32cb48376f7c9dbb5cb12339a56f163a087c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Cser=C3=A9p?= Date: Tue, 27 May 2025 06:17:52 +0200 Subject: [PATCH 3/4] Fix merge conflict resolution. --- .../parser/include/cppmetricsparser/cppmetricsparser.h | 1 + plugins/cpp_metrics/parser/src/cppmetricsparser.cpp | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index e75fa7d1c..9fabefd1c 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -212,6 +212,7 @@ class CppMetricsParser : public AbstractParser static const int afferentCouplingTypesPartitionMultiplier = 5; static const int efferentCouplingModulesPartitionMultiplier = 5; static const int afferentCouplingModulesPartitionMultiplier = 5; + static const int relationalCohesionPartitionMultiplier = 5; }; } // parser diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 8975f8361..c949be7a2 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -383,8 +383,6 @@ void CppMetricsParser::efferentTypeLevel() { typedef odb::query MemTypeQuery; typedef odb::query InheritanceQuery; - typedef odb::query ParamQuery; - typedef odb::query LocalQuery; typedef odb::query FuncQuery; std::set dependentTypes; @@ -612,7 +610,7 @@ void CppMetricsParser::relationalCohesionModuleLevel() parallelCalcMetric( "Relational cohesion at module level", - _threadCount * relationalCohesionPartitionMultiplier,// number of jobs; adjust for granularity + _threadCount * relationalCohesionPartitionMultiplier, // number of jobs; adjust for granularity getModulePathsQuery(), [&, this](const MetricsTasks& tasks) { From f6b46a70e55d6c461c224912903ceee1848449fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Cser=C3=A9p?= Date: Tue, 27 May 2025 08:23:00 +0200 Subject: [PATCH 4/4] Add module level C++ metrics to service info endpoint. --- .../cpp_metrics/model/include/model/cppfilemetrics.h | 6 +++--- plugins/cpp_metrics/service/cxxmetrics.thrift | 4 +++- .../cpp_metrics/service/src/cppmetricsservice.cpp | 12 ++++++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/plugins/cpp_metrics/model/include/model/cppfilemetrics.h b/plugins/cpp_metrics/model/include/model/cppfilemetrics.h index e231ab077..1edfa6740 100644 --- a/plugins/cpp_metrics/model/include/model/cppfilemetrics.h +++ b/plugins/cpp_metrics/model/include/model/cppfilemetrics.h @@ -13,9 +13,9 @@ struct CppFileMetrics { enum Type { - EFFERENT_MODULE, - AFFERENT_MODULE, - RELATIONAL_COHESION_MODULE + EFFERENT_MODULE = 1, + AFFERENT_MODULE = 2, + RELATIONAL_COHESION_MODULE = 3 }; #pragma db id auto diff --git a/plugins/cpp_metrics/service/cxxmetrics.thrift b/plugins/cpp_metrics/service/cxxmetrics.thrift index 08975f80e..65f3f383e 100644 --- a/plugins/cpp_metrics/service/cxxmetrics.thrift +++ b/plugins/cpp_metrics/service/cxxmetrics.thrift @@ -18,7 +18,9 @@ enum CppAstNodeMetricsType enum CppModuleMetricsType { - Placeholder = 1 + EfferentModule = 1, + AfferentModule = 2, + RelationalCohesionModule = 3 } struct CppAstNodeMetricsTypeName diff --git a/plugins/cpp_metrics/service/src/cppmetricsservice.cpp b/plugins/cpp_metrics/service/src/cppmetricsservice.cpp index 12785856e..a9f5a46dd 100644 --- a/plugins/cpp_metrics/service/src/cppmetricsservice.cpp +++ b/plugins/cpp_metrics/service/src/cppmetricsservice.cpp @@ -70,8 +70,16 @@ void CppMetricsServiceHandler::getCppModuleMetricsTypeNames( { CppModuleMetricsTypeName typeName; - typeName.type = CppModuleMetricsType::Placeholder; - typeName.name = "Placeholder"; + typeName.type = CppModuleMetricsType::EfferentModule; + typeName.name = "Efferent coupling of module"; + _return.push_back(typeName); + + typeName.type = CppModuleMetricsType::AfferentModule; + typeName.name = "Afferent coupling of module"; + _return.push_back(typeName); + + typeName.type = CppModuleMetricsType::RelationalCohesionModule; + typeName.name = "Relational cohesion of module"; _return.push_back(typeName); }