From 5285056302ac925fe0a69da5c72fcfcb717bd33e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Fri, 12 Apr 2024 11:53:57 +0200 Subject: [PATCH 1/7] Implementation of C++ metrics parallelization. --- plugins/cpp/model/include/model/cppfunction.h | 6 -- .../model/include/model/cppcohesionmetrics.h | 3 - .../cppmetricsparser/cppmetricsparser.h | 83 ++++++++++++++++++- .../parser/src/cppmetricsparser.cpp | 50 +++++------ 4 files changed, 101 insertions(+), 41 deletions(-) diff --git a/plugins/cpp/model/include/model/cppfunction.h b/plugins/cpp/model/include/model/cppfunction.h index 2492d9f07..1461296ac 100644 --- a/plugins/cpp/model/include/model/cppfunction.h +++ b/plugins/cpp/model/include/model/cppfunction.h @@ -53,9 +53,6 @@ struct CppFunctionParamCountWithId #pragma db column("count(" + Parameters::id + ")") std::size_t count; - - #pragma db column(File::path) - std::string filePath; }; #pragma db view \ @@ -77,9 +74,6 @@ struct CppFunctionMcCabe #pragma db column(CppFunction::mccabe) unsigned int mccabe; - - #pragma db column(File::path) - std::string filePath; }; } diff --git a/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h b/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h index bc8a20629..2e4ce93eb 100644 --- a/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h +++ b/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h @@ -23,9 +23,6 @@ struct CohesionCppRecordView #pragma db column(CppEntity::astNodeId) CppAstNodeId astNodeId; - - #pragma db column(File::path) - std::string filePath; }; #pragma db view \ diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 6dcfb0bc2..4990cd6b0 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -15,12 +15,41 @@ #include #include +#include namespace cc { namespace parser { - + +/// @brief Constructs an ODB query that you can use to filter only +/// the database records of the given parameter type whose path +/// is rooted under any of the specified filter paths. +/// @tparam TQueryParam The type of database records to query. +/// This type must represent an ODB view that has access to +/// (i.e. is also joined with) the File table. +/// @tparam TIter The iterator type of the filter paths. +/// @tparam TSentinel The type of the end of the filter paths. +/// @param begin_ The iterator referring to the first filter path. +/// @param end_ The sentinel for the end of the filter paths. +/// @return A query containing the disjunction of filters. +template +odb::query getFilterPathsQuery( + TIter begin_, + const TSentinel& end_) +{ + typedef typename odb::query::query_columns QParam; + const auto& QParamPath = QParam::File::path; + constexpr char ODBWildcard = '%'; + + assert(begin_ != end_ && "At least one filter path must be provided."); + odb::query query = QParamPath.like(*begin_ + ODBWildcard); + while (++begin_ != end_) + query = query || QParamPath.like(*begin_ + ODBWildcard); + return query; +} + + class CppMetricsParser : public AbstractParser { public: @@ -39,10 +68,60 @@ class CppMetricsParser : public AbstractParser // and member functions for every type. void lackOfCohesion(); + + /// @brief Constructs an ODB query that you can use to filter only + /// the database records of the given parameter type whose path + /// is rooted under any of this parser's input paths. + /// @tparam TQueryParam The type of database records to query. + /// This type must represent an ODB view that has access to + /// (i.e. is also joined with) the File table. + /// @return A query containing the disjunction of filters. + template + odb::query getFilterPathsQuery() const + { + return cc::parser::getFilterPathsQuery( + _inputPaths.begin(), _inputPaths.end()); + } + + /// @brief Calculates a metric by querying all objects of the + /// specified parameter type and passing them one-by-one to the + /// specified worker function on parallel threads. + /// This call blocks the caller thread until all workers are finished. + /// @tparam TQueryParam The type of parameters to query. + /// @param query_ A filter query for retrieving only + /// the eligible parameters for which a worker should be spawned. + /// @param worker_ The logic of the worker thread. + template + void parallelCalcMetric( + const odb::query& query_, + const std::function& worker_) + { + std::unique_ptr> pool = + util::make_thread_pool(_threadCount, worker_); + util::OdbTransaction {_ctx.db} ([&, this] + { + for (const TQueryParam& param : _ctx.db->query(query_)) + pool->enqueue(param); + }); + pool->wait(); + } + + /// @brief Calculates a metric by querying all objects of the + /// specified parameter type and passing them one-by-one to the + /// specified worker function on parallel threads. + /// This call blocks the caller thread until all workers are finished. + /// @tparam TQueryParam The type of parameters to query. + /// @param worker_ The logic of the worker thread. + template + void parallelCalcMetric( + const std::function& worker_) + { parallelCalcMetric(odb::query(), worker_); } + + + int _threadCount; std::vector _inputPaths; std::unordered_set _fileIdCache; std::unordered_map _astNodeIdCache; - std::unique_ptr> _pool; }; } // parser diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 7e8269a4d..612e32b6f 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -14,8 +14,6 @@ #include #include -#include -#include #include @@ -28,6 +26,7 @@ namespace fs = boost::filesystem; CppMetricsParser::CppMetricsParser(ParserContext& ctx_): AbstractParser(ctx_) { + _threadCount = _ctx.options["jobs"].as(); for (const std::string& path : _ctx.options["input"].as>()) _inputPaths.push_back(fs::canonical(path).string()); @@ -103,47 +102,44 @@ bool CppMetricsParser::cleanupDatabase() void CppMetricsParser::functionParameters() { - util::OdbTransaction {_ctx.db} ([&, this] + parallelCalcMetric( + getFilterPathsQuery(), + [&, this](const model::CppFunctionParamCountWithId& funParams) { - for (const model::CppFunctionParamCountWithId& paramCount - : _ctx.db->query()) + util::OdbTransaction {_ctx.db} ([&, this] { - // Skip functions that were included from external libraries. - if (!cc::util::isRootedUnderAnyOf(_inputPaths, paramCount.filePath)) - continue; - model::CppAstNodeMetrics funcParams; - funcParams.astNodeId = paramCount.id; + funcParams.astNodeId = funParams.id; funcParams.type = model::CppAstNodeMetrics::Type::PARAMETER_COUNT; - funcParams.value = paramCount.count; + funcParams.value = funParams.count; _ctx.db->persist(funcParams); - } + }); }); } void CppMetricsParser::functionMcCabe() { - util::OdbTransaction {_ctx.db} ([&, this] + parallelCalcMetric( + getFilterPathsQuery(), + [&, this](const model::CppFunctionMcCabe& function) { - for (const model::CppFunctionMcCabe& function - : _ctx.db->query()) + util::OdbTransaction {_ctx.db} ([&, this] { - // Skip functions that were included from external libraries. - if (!cc::util::isRootedUnderAnyOf(_inputPaths, function.filePath)) - continue; - model::CppAstNodeMetrics funcMcCabe; funcMcCabe.astNodeId = function.astNodeId; funcMcCabe.type = model::CppAstNodeMetrics::Type::MCCABE; funcMcCabe.value = function.mccabe; _ctx.db->persist(funcMcCabe); - } + }); }); } void CppMetricsParser::lackOfCohesion() { - util::OdbTransaction {_ctx.db} ([&, this] + // Calculate the cohesion metric for all types on parallel threads. + parallelCalcMetric( + getFilterPathsQuery(), + [&, this](const model::CohesionCppRecordView& type) { // Simplify some type names for readability. typedef std::uint64_t HashType; @@ -157,15 +153,9 @@ void CppMetricsParser::lackOfCohesion() typedef odb::query::query_columns QNode; const auto& QNodeFilePath = QNode::File::path; const auto& QNodeRange = QNode::CppAstNode::location.range; - - // Calculate the cohesion metric for all types. - for (const model::CohesionCppRecordView& type - : _ctx.db->query()) + + util::OdbTransaction {_ctx.db} ([&, this] { - // Skip types that were included from external libraries. - if (!cc::util::isRootedUnderAnyOf(_inputPaths, type.filePath)) - continue; - std::unordered_set fieldHashes; // Query all fields of the current type. for (const model::CohesionCppFieldView& field @@ -242,7 +232,7 @@ void CppMetricsParser::lackOfCohesion() lcm_hs.value = trivial ? 0.0 : singular ? NAN : ((dM - dC / dF) / (dM - 1.0)); _ctx.db->persist(lcm_hs); - } + }); }); } From 3e2b26edc581278e1b2862cd6de2e36bfa5e1a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Thu, 18 Apr 2024 11:13:56 +0200 Subject: [PATCH 2/7] Implementation of query buffering, job partitioning, and progress reporting. --- .../cppmetricsparser/cppmetricsparser.h | 99 ++++++++- .../parser/src/cppmetricsparser.cpp | 203 ++++++++++-------- 2 files changed, 202 insertions(+), 100 deletions(-) diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 4990cd6b0..f4a4f37dc 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -49,6 +49,32 @@ odb::query getFilterPathsQuery( return query; } +template +class MetricsTasks +{ +public: + typedef typename std::vector::const_iterator TTaskIter; + + const TTaskIter& begin() const { return _begin; } + const TTaskIter& end() const { return _end; } + std::size_t size() const { return _size; } + + MetricsTasks( + const TTaskIter& begin_, + const TTaskIter& end_, + std::size_t size_ + ) : + _begin(begin_), + _end(end_), + _size(size_) + {} + +private: + TTaskIter _begin; + TTaskIter _end; + std::size_t _size; +}; + class CppMetricsParser : public AbstractParser { @@ -88,22 +114,73 @@ class CppMetricsParser : public AbstractParser /// specified worker function on parallel threads. /// This call blocks the caller thread until all workers are finished. /// @tparam TQueryParam The type of parameters to query. + /// @param name_ The name of the metric (for progress logging). + /// @param partitions_ The number of jobs to partition the query into. /// @param query_ A filter query for retrieving only /// the eligible parameters for which a worker should be spawned. /// @param worker_ The logic of the worker thread. template void parallelCalcMetric( + const char* name_, + std::size_t partitions_, const odb::query& query_, - const std::function& worker_) + const std::function&)>& worker_) { - std::unique_ptr> pool = - util::make_thread_pool(_threadCount, worker_); + typedef MetricsTasks TMetricsTasks; + typedef typename TMetricsTasks::TTaskIter TTaskIter; + typedef std::pair TJobParam; + + // Define the thread pool and job wrapper function. + LOG(info) << name_ << " : Collecting jobs from database..."; + std::unique_ptr> pool = + util::make_thread_pool(_threadCount, + [&](const TJobParam& job) + { + LOG(info) << '(' << job.first << '/' << partitions_ + << ") " << name_; + worker_(job.second); + }); + + // Cache the results of the query that will be dispatched to workers. + std::vector tasks; util::OdbTransaction {_ctx.db} ([&, this] { + // Storing the result directly and then calling odb::result<>::cache() + // on it does not work: odb::result<>::size() will always throw + // odb::result_not_cached. As of writing, this is a limitation of SQLite. + // So we fall back to the old-fashioned way: std::vector<> in memory. for (const TQueryParam& param : _ctx.db->query(query_)) - pool->enqueue(param); + tasks.emplace_back(param); }); + + // Ensure that all workers receive at least one task. + std::size_t taskCount = tasks.size(); + if (partitions_ > taskCount) + partitions_ = taskCount; + + // Dispatch jobs to workers in discrete packets. + LOG(info) << name_ << " : Dispatching jobs on " + << _threadCount << " thread(s)..."; + std::size_t prev = 0; + TTaskIter it_prev = tasks.cbegin(); + + std::size_t i = 0; + while (i < partitions_) + { + std::size_t next = taskCount * ++i / partitions_; + std::size_t size = next - prev; + TTaskIter it_next = it_prev; + std::advance(it_next, size); + + pool->enqueue(TJobParam(i, TMetricsTasks(it_prev, it_next, size))); + + prev = next; + it_prev = it_next; + } + + // Await the termination of all workers. pool->wait(); + LOG(info) << name_ << " : Calculation finished."; } /// @brief Calculates a metric by querying all objects of the @@ -111,11 +188,21 @@ class CppMetricsParser : public AbstractParser /// specified worker function on parallel threads. /// This call blocks the caller thread until all workers are finished. /// @tparam TQueryParam The type of parameters to query. + /// @param name_ The name of the metric (for progress logging). + /// @param partitions_ The number of jobs to partition the query into. /// @param worker_ The logic of the worker thread. template void parallelCalcMetric( - const std::function& worker_) - { parallelCalcMetric(odb::query(), worker_); } + const char* name_, + std::size_t partitions_, + const std::function&)>& worker_) + { + parallelCalcMetric( + name_, + partitions_, + odb::query(), + worker_); + } int _threadCount; diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 612e32b6f..ae6aab9bf 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -103,16 +103,21 @@ bool CppMetricsParser::cleanupDatabase() void CppMetricsParser::functionParameters() { parallelCalcMetric( + "Function parameters", + _threadCount * 5,// number of jobs; adjust for granularity getFilterPathsQuery(), - [&, this](const model::CppFunctionParamCountWithId& funParams) + [&, this](const MetricsTasks& tasks) { util::OdbTransaction {_ctx.db} ([&, this] { - model::CppAstNodeMetrics funcParams; - funcParams.astNodeId = funParams.id; - funcParams.type = model::CppAstNodeMetrics::Type::PARAMETER_COUNT; - funcParams.value = funParams.count; - _ctx.db->persist(funcParams); + for (const model::CppFunctionParamCountWithId& param : tasks) + { + model::CppAstNodeMetrics funcParams; + funcParams.astNodeId = param.id; + funcParams.type = model::CppAstNodeMetrics::Type::PARAMETER_COUNT; + funcParams.value = param.count; + _ctx.db->persist(funcParams); + } }); }); } @@ -120,16 +125,21 @@ void CppMetricsParser::functionParameters() void CppMetricsParser::functionMcCabe() { parallelCalcMetric( + "Function-level McCabe", + _threadCount * 5,// number of jobs; adjust for granularity getFilterPathsQuery(), - [&, this](const model::CppFunctionMcCabe& function) + [&, this](const MetricsTasks& tasks) { util::OdbTransaction {_ctx.db} ([&, this] { - model::CppAstNodeMetrics funcMcCabe; - funcMcCabe.astNodeId = function.astNodeId; - funcMcCabe.type = model::CppAstNodeMetrics::Type::MCCABE; - funcMcCabe.value = function.mccabe; - _ctx.db->persist(funcMcCabe); + for (const model::CppFunctionMcCabe& param : tasks) + { + model::CppAstNodeMetrics funcMcCabe; + funcMcCabe.astNodeId = param.astNodeId; + funcMcCabe.type = model::CppAstNodeMetrics::Type::MCCABE; + funcMcCabe.value = param.mccabe; + _ctx.db->persist(funcMcCabe); + } }); }); } @@ -138,100 +148,105 @@ void CppMetricsParser::lackOfCohesion() { // Calculate the cohesion metric for all types on parallel threads. parallelCalcMetric( + "Lack of cohesion", + _threadCount * 25,// number of jobs; adjust for granularity getFilterPathsQuery(), - [&, this](const model::CohesionCppRecordView& type) + [&, this](const MetricsTasks& tasks) { - // Simplify some type names for readability. - typedef std::uint64_t HashType; - - typedef odb::query::query_columns QField; - const auto& QFieldTypeHash = QField::CppMemberType::typeHash; - - typedef odb::query::query_columns QMethod; - const auto& QMethodTypeHash = QMethod::CppMemberType::typeHash; - - typedef odb::query::query_columns QNode; - const auto& QNodeFilePath = QNode::File::path; - const auto& QNodeRange = QNode::CppAstNode::location.range; - util::OdbTransaction {_ctx.db} ([&, this] { - std::unordered_set fieldHashes; - // Query all fields of the current type. - for (const model::CohesionCppFieldView& field - : _ctx.db->query( - QFieldTypeHash == type.entityHash - )) - { - // Record these fields for later use. - fieldHashes.insert(field.entityHash); - } - std::size_t fieldCount = fieldHashes.size(); - - std::size_t methodCount = 0; - std::size_t totalCohesion = 0; - // Query all methods of the current type. - for (const model::CohesionCppMethodView& method - : _ctx.db->query( - QMethodTypeHash == type.entityHash - )) + // Simplify some type names for readability. + typedef std::uint64_t HashType; + + typedef odb::query::query_columns QField; + const auto& QFieldTypeHash = QField::CppMemberType::typeHash; + + typedef odb::query::query_columns QMethod; + const auto& QMethodTypeHash = QMethod::CppMemberType::typeHash; + + typedef odb::query::query_columns QNode; + const auto& QNodeFilePath = QNode::File::path; + const auto& QNodeRange = QNode::CppAstNode::location.range; + + for (const model::CohesionCppRecordView& type : tasks) { - // Do not consider methods with no explicit bodies. - const model::Position start(method.startLine, method.startColumn); - const model::Position end(method.endLine, method.endColumn); - if (start < end) + std::unordered_set fieldHashes; + // Query all fields of the current type. + for (const model::CohesionCppFieldView& field + : _ctx.db->query( + QFieldTypeHash == type.entityHash + )) { - std::unordered_set usedFields; - - // Query all AST nodes that use a variable for reading or writing... - for (const model::CohesionCppAstNodeView& node - : _ctx.db->query( - // ... in the same file as the current method - (QNodeFilePath == method.filePath && - // ... within the textual scope of the current method's body. - (QNodeRange.start.line >= start.line - || (QNodeRange.start.line == start.line - && QNodeRange.start.column >= start.column)) && - (QNodeRange.end.line <= end.line - || (QNodeRange.end.line == end.line - && QNodeRange.end.column <= end.column))) - )) + // Record these fields for later use. + fieldHashes.insert(field.entityHash); + } + std::size_t fieldCount = fieldHashes.size(); + + std::size_t methodCount = 0; + std::size_t totalCohesion = 0; + // Query all methods of the current type. + for (const model::CohesionCppMethodView& method + : _ctx.db->query( + QMethodTypeHash == type.entityHash + )) + { + // Do not consider methods with no explicit bodies. + const model::Position start(method.startLine, method.startColumn); + const model::Position end(method.endLine, method.endColumn); + if (start < end) { - // If this AST node is a reference to a field of the type... - if (fieldHashes.find(node.entityHash) != fieldHashes.end()) + std::unordered_set usedFields; + + // Query AST nodes that use a variable for reading or writing... + for (const model::CohesionCppAstNodeView& node + : _ctx.db->query( + // ... in the same file as the current method + (QNodeFilePath == method.filePath && + // ... within the textual scope of the current method's body. + (QNodeRange.start.line >= start.line + || (QNodeRange.start.line == start.line + && QNodeRange.start.column >= start.column)) && + (QNodeRange.end.line <= end.line + || (QNodeRange.end.line == end.line + && QNodeRange.end.column <= end.column))) + )) { - // ... then mark it as used by this method. - usedFields.insert(node.entityHash); + // If this AST node is a reference to a field of the type... + if (fieldHashes.find(node.entityHash) != fieldHashes.end()) + { + // ... then mark it as used by this method. + usedFields.insert(node.entityHash); + } } + + ++methodCount; + totalCohesion += usedFields.size(); } - - ++methodCount; - totalCohesion += usedFields.size(); } - } - // Calculate and record metrics. - const double dF = fieldCount; - const double dM = methodCount; - const double dC = totalCohesion; - const bool trivial = fieldCount == 0 || methodCount == 0; - const bool singular = methodCount == 1; - - // Standard lack of cohesion (range: [0,1]) - model::CppAstNodeMetrics lcm; - lcm.astNodeId = type.astNodeId; - lcm.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION; - lcm.value = trivial ? 0.0 : - (1.0 - dC / (dM * dF)); - _ctx.db->persist(lcm); - - // Henderson-Sellers variant (range: [0,2]) - model::CppAstNodeMetrics lcm_hs; - lcm_hs.astNodeId = type.astNodeId; - lcm_hs.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION_HS; - lcm_hs.value = trivial ? 0.0 : singular ? NAN : - ((dM - dC / dF) / (dM - 1.0)); - _ctx.db->persist(lcm_hs); + // Calculate and record metrics. + const double dF = fieldCount; + const double dM = methodCount; + const double dC = totalCohesion; + const bool trivial = fieldCount == 0 || methodCount == 0; + const bool singular = methodCount == 1; + + // Standard lack of cohesion (range: [0,1]) + model::CppAstNodeMetrics lcm; + lcm.astNodeId = type.astNodeId; + lcm.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION; + lcm.value = trivial ? 0.0 : + (1.0 - dC / (dM * dF)); + _ctx.db->persist(lcm); + + // Henderson-Sellers variant (range: [0,2]) + model::CppAstNodeMetrics lcm_hs; + lcm_hs.astNodeId = type.astNodeId; + lcm_hs.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION_HS; + lcm_hs.value = trivial ? 0.0 : singular ? NAN : + ((dM - dC / dF) / (dM - 1.0)); + _ctx.db->persist(lcm_hs); + } }); }); } From 93ce439771c0bd529094d9b7ef77b0c0ea6a9d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Fri, 12 Apr 2024 11:53:57 +0200 Subject: [PATCH 3/7] Implementation of C++ metrics parallelization. --- plugins/cpp/model/include/model/cppfunction.h | 6 -- .../model/include/model/cppcohesionmetrics.h | 3 - .../cppmetricsparser/cppmetricsparser.h | 83 ++++++++++++++++++- .../parser/src/cppmetricsparser.cpp | 50 +++++------ 4 files changed, 101 insertions(+), 41 deletions(-) diff --git a/plugins/cpp/model/include/model/cppfunction.h b/plugins/cpp/model/include/model/cppfunction.h index 938f04aa4..b19190c6f 100644 --- a/plugins/cpp/model/include/model/cppfunction.h +++ b/plugins/cpp/model/include/model/cppfunction.h @@ -56,9 +56,6 @@ struct CppFunctionParamCountWithId #pragma db column("count(" + Parameters::id + ")") std::size_t count; - - #pragma db column(File::path) - std::string filePath; }; #pragma db view \ @@ -80,9 +77,6 @@ struct CppFunctionMcCabe #pragma db column(CppFunction::mccabe) unsigned int mccabe; - - #pragma db column(File::path) - std::string filePath; }; #pragma db view \ diff --git a/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h b/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h index bc8a20629..2e4ce93eb 100644 --- a/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h +++ b/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h @@ -23,9 +23,6 @@ struct CohesionCppRecordView #pragma db column(CppEntity::astNodeId) CppAstNodeId astNodeId; - - #pragma db column(File::path) - std::string filePath; }; #pragma db view \ diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 5abe1af83..58e7ae18f 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -15,12 +15,41 @@ #include #include +#include namespace cc { namespace parser { - + +/// @brief Constructs an ODB query that you can use to filter only +/// the database records of the given parameter type whose path +/// is rooted under any of the specified filter paths. +/// @tparam TQueryParam The type of database records to query. +/// This type must represent an ODB view that has access to +/// (i.e. is also joined with) the File table. +/// @tparam TIter The iterator type of the filter paths. +/// @tparam TSentinel The type of the end of the filter paths. +/// @param begin_ The iterator referring to the first filter path. +/// @param end_ The sentinel for the end of the filter paths. +/// @return A query containing the disjunction of filters. +template +odb::query getFilterPathsQuery( + TIter begin_, + const TSentinel& end_) +{ + typedef typename odb::query::query_columns QParam; + const auto& QParamPath = QParam::File::path; + constexpr char ODBWildcard = '%'; + + assert(begin_ != end_ && "At least one filter path must be provided."); + odb::query query = QParamPath.like(*begin_ + ODBWildcard); + while (++begin_ != end_) + query = query || QParamPath.like(*begin_ + ODBWildcard); + return query; +} + + class CppMetricsParser : public AbstractParser { public: @@ -41,10 +70,60 @@ class CppMetricsParser : public AbstractParser // and member functions for every type. void lackOfCohesion(); + + /// @brief Constructs an ODB query that you can use to filter only + /// the database records of the given parameter type whose path + /// is rooted under any of this parser's input paths. + /// @tparam TQueryParam The type of database records to query. + /// This type must represent an ODB view that has access to + /// (i.e. is also joined with) the File table. + /// @return A query containing the disjunction of filters. + template + odb::query getFilterPathsQuery() const + { + return cc::parser::getFilterPathsQuery( + _inputPaths.begin(), _inputPaths.end()); + } + + /// @brief Calculates a metric by querying all objects of the + /// specified parameter type and passing them one-by-one to the + /// specified worker function on parallel threads. + /// This call blocks the caller thread until all workers are finished. + /// @tparam TQueryParam The type of parameters to query. + /// @param query_ A filter query for retrieving only + /// the eligible parameters for which a worker should be spawned. + /// @param worker_ The logic of the worker thread. + template + void parallelCalcMetric( + const odb::query& query_, + const std::function& worker_) + { + std::unique_ptr> pool = + util::make_thread_pool(_threadCount, worker_); + util::OdbTransaction {_ctx.db} ([&, this] + { + for (const TQueryParam& param : _ctx.db->query(query_)) + pool->enqueue(param); + }); + pool->wait(); + } + + /// @brief Calculates a metric by querying all objects of the + /// specified parameter type and passing them one-by-one to the + /// specified worker function on parallel threads. + /// This call blocks the caller thread until all workers are finished. + /// @tparam TQueryParam The type of parameters to query. + /// @param worker_ The logic of the worker thread. + template + void parallelCalcMetric( + const std::function& worker_) + { parallelCalcMetric(odb::query(), worker_); } + + + int _threadCount; std::vector _inputPaths; std::unordered_set _fileIdCache; std::unordered_map _astNodeIdCache; - std::unique_ptr> _pool; }; } // parser diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index c7404d437..009fb7794 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -14,8 +14,6 @@ #include #include -#include -#include #include @@ -28,6 +26,7 @@ namespace fs = boost::filesystem; CppMetricsParser::CppMetricsParser(ParserContext& ctx_): AbstractParser(ctx_) { + _threadCount = _ctx.options["jobs"].as(); for (const std::string& path : _ctx.options["input"].as>()) _inputPaths.push_back(fs::canonical(path).string()); @@ -101,41 +100,35 @@ bool CppMetricsParser::cleanupDatabase() void CppMetricsParser::functionParameters() { - util::OdbTransaction {_ctx.db} ([&, this] + parallelCalcMetric( + getFilterPathsQuery(), + [&, this](const model::CppFunctionParamCountWithId& funParams) { - for (const model::CppFunctionParamCountWithId& paramCount - : _ctx.db->query()) + util::OdbTransaction {_ctx.db} ([&, this] { - // Skip functions that were included from external libraries. - if (!cc::util::isRootedUnderAnyOf(_inputPaths, paramCount.filePath)) - continue; - model::CppAstNodeMetrics funcParams; - funcParams.astNodeId = paramCount.id; + funcParams.astNodeId = funParams.id; funcParams.type = model::CppAstNodeMetrics::Type::PARAMETER_COUNT; - funcParams.value = paramCount.count; + funcParams.value = funParams.count; _ctx.db->persist(funcParams); - } + }); }); } void CppMetricsParser::functionMcCabe() { - util::OdbTransaction {_ctx.db} ([&, this] + parallelCalcMetric( + getFilterPathsQuery(), + [&, this](const model::CppFunctionMcCabe& function) { - for (const model::CppFunctionMcCabe& function - : _ctx.db->query()) + util::OdbTransaction {_ctx.db} ([&, this] { - // Skip functions that were included from external libraries. - if (!cc::util::isRootedUnderAnyOf(_inputPaths, function.filePath)) - continue; - model::CppAstNodeMetrics funcMcCabe; funcMcCabe.astNodeId = function.astNodeId; funcMcCabe.type = model::CppAstNodeMetrics::Type::MCCABE; funcMcCabe.value = function.mccabe; _ctx.db->persist(funcMcCabe); - } + }); }); } @@ -165,7 +158,10 @@ void CppMetricsParser::functionBumpyRoad() void CppMetricsParser::lackOfCohesion() { - util::OdbTransaction {_ctx.db} ([&, this] + // Calculate the cohesion metric for all types on parallel threads. + parallelCalcMetric( + getFilterPathsQuery(), + [&, this](const model::CohesionCppRecordView& type) { // Simplify some type names for readability. typedef std::uint64_t HashType; @@ -179,15 +175,9 @@ void CppMetricsParser::lackOfCohesion() typedef odb::query::query_columns QNode; const auto& QNodeFilePath = QNode::File::path; const auto& QNodeRange = QNode::CppAstNode::location.range; - - // Calculate the cohesion metric for all types. - for (const model::CohesionCppRecordView& type - : _ctx.db->query()) + + util::OdbTransaction {_ctx.db} ([&, this] { - // Skip types that were included from external libraries. - if (!cc::util::isRootedUnderAnyOf(_inputPaths, type.filePath)) - continue; - std::unordered_set fieldHashes; // Query all fields of the current type. for (const model::CohesionCppFieldView& field @@ -264,7 +254,7 @@ void CppMetricsParser::lackOfCohesion() lcm_hs.value = trivial ? 0.0 : singular ? NAN : ((dM - dC / dF) / (dM - 1.0)); _ctx.db->persist(lcm_hs); - } + }); }); } From 39dce721b77a028ade73af40cbe434522e820bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Thu, 18 Apr 2024 11:13:56 +0200 Subject: [PATCH 4/7] Implementation of query buffering, job partitioning, and progress reporting. --- .../cppmetricsparser/cppmetricsparser.h | 99 ++++++++- .../parser/src/cppmetricsparser.cpp | 203 ++++++++++-------- 2 files changed, 202 insertions(+), 100 deletions(-) diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 58e7ae18f..ae37913e2 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -49,6 +49,32 @@ odb::query getFilterPathsQuery( return query; } +template +class MetricsTasks +{ +public: + typedef typename std::vector::const_iterator TTaskIter; + + const TTaskIter& begin() const { return _begin; } + const TTaskIter& end() const { return _end; } + std::size_t size() const { return _size; } + + MetricsTasks( + const TTaskIter& begin_, + const TTaskIter& end_, + std::size_t size_ + ) : + _begin(begin_), + _end(end_), + _size(size_) + {} + +private: + TTaskIter _begin; + TTaskIter _end; + std::size_t _size; +}; + class CppMetricsParser : public AbstractParser { @@ -90,22 +116,73 @@ class CppMetricsParser : public AbstractParser /// specified worker function on parallel threads. /// This call blocks the caller thread until all workers are finished. /// @tparam TQueryParam The type of parameters to query. + /// @param name_ The name of the metric (for progress logging). + /// @param partitions_ The number of jobs to partition the query into. /// @param query_ A filter query for retrieving only /// the eligible parameters for which a worker should be spawned. /// @param worker_ The logic of the worker thread. template void parallelCalcMetric( + const char* name_, + std::size_t partitions_, const odb::query& query_, - const std::function& worker_) + const std::function&)>& worker_) { - std::unique_ptr> pool = - util::make_thread_pool(_threadCount, worker_); + typedef MetricsTasks TMetricsTasks; + typedef typename TMetricsTasks::TTaskIter TTaskIter; + typedef std::pair TJobParam; + + // Define the thread pool and job wrapper function. + LOG(info) << name_ << " : Collecting jobs from database..."; + std::unique_ptr> pool = + util::make_thread_pool(_threadCount, + [&](const TJobParam& job) + { + LOG(info) << '(' << job.first << '/' << partitions_ + << ") " << name_; + worker_(job.second); + }); + + // Cache the results of the query that will be dispatched to workers. + std::vector tasks; util::OdbTransaction {_ctx.db} ([&, this] { + // Storing the result directly and then calling odb::result<>::cache() + // on it does not work: odb::result<>::size() will always throw + // odb::result_not_cached. As of writing, this is a limitation of SQLite. + // So we fall back to the old-fashioned way: std::vector<> in memory. for (const TQueryParam& param : _ctx.db->query(query_)) - pool->enqueue(param); + tasks.emplace_back(param); }); + + // Ensure that all workers receive at least one task. + std::size_t taskCount = tasks.size(); + if (partitions_ > taskCount) + partitions_ = taskCount; + + // Dispatch jobs to workers in discrete packets. + LOG(info) << name_ << " : Dispatching jobs on " + << _threadCount << " thread(s)..."; + std::size_t prev = 0; + TTaskIter it_prev = tasks.cbegin(); + + std::size_t i = 0; + while (i < partitions_) + { + std::size_t next = taskCount * ++i / partitions_; + std::size_t size = next - prev; + TTaskIter it_next = it_prev; + std::advance(it_next, size); + + pool->enqueue(TJobParam(i, TMetricsTasks(it_prev, it_next, size))); + + prev = next; + it_prev = it_next; + } + + // Await the termination of all workers. pool->wait(); + LOG(info) << name_ << " : Calculation finished."; } /// @brief Calculates a metric by querying all objects of the @@ -113,11 +190,21 @@ class CppMetricsParser : public AbstractParser /// specified worker function on parallel threads. /// This call blocks the caller thread until all workers are finished. /// @tparam TQueryParam The type of parameters to query. + /// @param name_ The name of the metric (for progress logging). + /// @param partitions_ The number of jobs to partition the query into. /// @param worker_ The logic of the worker thread. template void parallelCalcMetric( - const std::function& worker_) - { parallelCalcMetric(odb::query(), worker_); } + const char* name_, + std::size_t partitions_, + const std::function&)>& worker_) + { + parallelCalcMetric( + name_, + partitions_, + odb::query(), + worker_); + } int _threadCount; diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 009fb7794..582c5b90b 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -101,16 +101,21 @@ bool CppMetricsParser::cleanupDatabase() void CppMetricsParser::functionParameters() { parallelCalcMetric( + "Function parameters", + _threadCount * 5,// number of jobs; adjust for granularity getFilterPathsQuery(), - [&, this](const model::CppFunctionParamCountWithId& funParams) + [&, this](const MetricsTasks& tasks) { util::OdbTransaction {_ctx.db} ([&, this] { - model::CppAstNodeMetrics funcParams; - funcParams.astNodeId = funParams.id; - funcParams.type = model::CppAstNodeMetrics::Type::PARAMETER_COUNT; - funcParams.value = funParams.count; - _ctx.db->persist(funcParams); + for (const model::CppFunctionParamCountWithId& param : tasks) + { + model::CppAstNodeMetrics funcParams; + funcParams.astNodeId = param.id; + funcParams.type = model::CppAstNodeMetrics::Type::PARAMETER_COUNT; + funcParams.value = param.count; + _ctx.db->persist(funcParams); + } }); }); } @@ -118,16 +123,21 @@ void CppMetricsParser::functionParameters() void CppMetricsParser::functionMcCabe() { parallelCalcMetric( + "Function-level McCabe", + _threadCount * 5,// number of jobs; adjust for granularity getFilterPathsQuery(), - [&, this](const model::CppFunctionMcCabe& function) + [&, this](const MetricsTasks& tasks) { util::OdbTransaction {_ctx.db} ([&, this] { - model::CppAstNodeMetrics funcMcCabe; - funcMcCabe.astNodeId = function.astNodeId; - funcMcCabe.type = model::CppAstNodeMetrics::Type::MCCABE; - funcMcCabe.value = function.mccabe; - _ctx.db->persist(funcMcCabe); + for (const model::CppFunctionMcCabe& param : tasks) + { + model::CppAstNodeMetrics funcMcCabe; + funcMcCabe.astNodeId = param.astNodeId; + funcMcCabe.type = model::CppAstNodeMetrics::Type::MCCABE; + funcMcCabe.value = param.mccabe; + _ctx.db->persist(funcMcCabe); + } }); }); } @@ -160,100 +170,105 @@ void CppMetricsParser::lackOfCohesion() { // Calculate the cohesion metric for all types on parallel threads. parallelCalcMetric( + "Lack of cohesion", + _threadCount * 25,// number of jobs; adjust for granularity getFilterPathsQuery(), - [&, this](const model::CohesionCppRecordView& type) + [&, this](const MetricsTasks& tasks) { - // Simplify some type names for readability. - typedef std::uint64_t HashType; - - typedef odb::query::query_columns QField; - const auto& QFieldTypeHash = QField::CppMemberType::typeHash; - - typedef odb::query::query_columns QMethod; - const auto& QMethodTypeHash = QMethod::CppMemberType::typeHash; - - typedef odb::query::query_columns QNode; - const auto& QNodeFilePath = QNode::File::path; - const auto& QNodeRange = QNode::CppAstNode::location.range; - util::OdbTransaction {_ctx.db} ([&, this] { - std::unordered_set fieldHashes; - // Query all fields of the current type. - for (const model::CohesionCppFieldView& field - : _ctx.db->query( - QFieldTypeHash == type.entityHash - )) - { - // Record these fields for later use. - fieldHashes.insert(field.entityHash); - } - std::size_t fieldCount = fieldHashes.size(); - - std::size_t methodCount = 0; - std::size_t totalCohesion = 0; - // Query all methods of the current type. - for (const model::CohesionCppMethodView& method - : _ctx.db->query( - QMethodTypeHash == type.entityHash - )) + // Simplify some type names for readability. + typedef std::uint64_t HashType; + + typedef odb::query::query_columns QField; + const auto& QFieldTypeHash = QField::CppMemberType::typeHash; + + typedef odb::query::query_columns QMethod; + const auto& QMethodTypeHash = QMethod::CppMemberType::typeHash; + + typedef odb::query::query_columns QNode; + const auto& QNodeFilePath = QNode::File::path; + const auto& QNodeRange = QNode::CppAstNode::location.range; + + for (const model::CohesionCppRecordView& type : tasks) { - // Do not consider methods with no explicit bodies. - const model::Position start(method.startLine, method.startColumn); - const model::Position end(method.endLine, method.endColumn); - if (start < end) + std::unordered_set fieldHashes; + // Query all fields of the current type. + for (const model::CohesionCppFieldView& field + : _ctx.db->query( + QFieldTypeHash == type.entityHash + )) + { + // Record these fields for later use. + fieldHashes.insert(field.entityHash); + } + std::size_t fieldCount = fieldHashes.size(); + + std::size_t methodCount = 0; + std::size_t totalCohesion = 0; + // Query all methods of the current type. + for (const model::CohesionCppMethodView& method + : _ctx.db->query( + QMethodTypeHash == type.entityHash + )) { - std::unordered_set usedFields; - - // Query all AST nodes that use a variable for reading or writing... - for (const model::CohesionCppAstNodeView& node - : _ctx.db->query( - // ... in the same file as the current method - (QNodeFilePath == method.filePath && - // ... within the textual scope of the current method's body. - (QNodeRange.start.line >= start.line - || (QNodeRange.start.line == start.line - && QNodeRange.start.column >= start.column)) && - (QNodeRange.end.line <= end.line - || (QNodeRange.end.line == end.line - && QNodeRange.end.column <= end.column))) - )) + // Do not consider methods with no explicit bodies. + const model::Position start(method.startLine, method.startColumn); + const model::Position end(method.endLine, method.endColumn); + if (start < end) { - // If this AST node is a reference to a field of the type... - if (fieldHashes.find(node.entityHash) != fieldHashes.end()) + std::unordered_set usedFields; + + // Query AST nodes that use a variable for reading or writing... + for (const model::CohesionCppAstNodeView& node + : _ctx.db->query( + // ... in the same file as the current method + (QNodeFilePath == method.filePath && + // ... within the textual scope of the current method's body. + (QNodeRange.start.line >= start.line + || (QNodeRange.start.line == start.line + && QNodeRange.start.column >= start.column)) && + (QNodeRange.end.line <= end.line + || (QNodeRange.end.line == end.line + && QNodeRange.end.column <= end.column))) + )) { - // ... then mark it as used by this method. - usedFields.insert(node.entityHash); + // If this AST node is a reference to a field of the type... + if (fieldHashes.find(node.entityHash) != fieldHashes.end()) + { + // ... then mark it as used by this method. + usedFields.insert(node.entityHash); + } } + + ++methodCount; + totalCohesion += usedFields.size(); } - - ++methodCount; - totalCohesion += usedFields.size(); } - } - // Calculate and record metrics. - const double dF = fieldCount; - const double dM = methodCount; - const double dC = totalCohesion; - const bool trivial = fieldCount == 0 || methodCount == 0; - const bool singular = methodCount == 1; - - // Standard lack of cohesion (range: [0,1]) - model::CppAstNodeMetrics lcm; - lcm.astNodeId = type.astNodeId; - lcm.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION; - lcm.value = trivial ? 0.0 : - (1.0 - dC / (dM * dF)); - _ctx.db->persist(lcm); - - // Henderson-Sellers variant (range: [0,2]) - model::CppAstNodeMetrics lcm_hs; - lcm_hs.astNodeId = type.astNodeId; - lcm_hs.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION_HS; - lcm_hs.value = trivial ? 0.0 : singular ? NAN : - ((dM - dC / dF) / (dM - 1.0)); - _ctx.db->persist(lcm_hs); + // Calculate and record metrics. + const double dF = fieldCount; + const double dM = methodCount; + const double dC = totalCohesion; + const bool trivial = fieldCount == 0 || methodCount == 0; + const bool singular = methodCount == 1; + + // Standard lack of cohesion (range: [0,1]) + model::CppAstNodeMetrics lcm; + lcm.astNodeId = type.astNodeId; + lcm.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION; + lcm.value = trivial ? 0.0 : + (1.0 - dC / (dM * dF)); + _ctx.db->persist(lcm); + + // Henderson-Sellers variant (range: [0,2]) + model::CppAstNodeMetrics lcm_hs; + lcm_hs.astNodeId = type.astNodeId; + lcm_hs.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION_HS; + lcm_hs.value = trivial ? 0.0 : singular ? NAN : + ((dM - dC / dF) / (dM - 1.0)); + _ctx.db->persist(lcm_hs); + } }); }); } From 4e8548224c369b6a0109a6254b2fca288fa8afd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCkki=20D=C3=A1niel?= Date: Sat, 11 May 2024 09:30:58 +0200 Subject: [PATCH 5/7] Parallelization of the bumpy road metric. --- plugins/cpp/model/include/model/cppfunction.h | 3 -- .../parser/src/cppmetricsparser.cpp | 38 ++++++++++--------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/plugins/cpp/model/include/model/cppfunction.h b/plugins/cpp/model/include/model/cppfunction.h index b19190c6f..7eca88a29 100644 --- a/plugins/cpp/model/include/model/cppfunction.h +++ b/plugins/cpp/model/include/model/cppfunction.h @@ -93,9 +93,6 @@ struct CppFunctionBumpyRoad #pragma db column(CppFunction::statementCount) unsigned int statementCount; - - #pragma db column(File::path) - std::string filePath; }; } diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 05f5930b5..dcb3ef77c 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -98,7 +98,6 @@ bool CppMetricsParser::cleanupDatabase() return true; } - void CppMetricsParser::functionParameters() { parallelCalcMetric( @@ -145,25 +144,28 @@ void CppMetricsParser::functionMcCabe() void CppMetricsParser::functionBumpyRoad() { - util::OdbTransaction {_ctx.db} ([&, this] + // Calculate the bumpy road metric for all types on parallel threads. + parallelCalcMetric( + "Bumpy road complexity", + _threadCount * 5,// number of jobs; adjust for granularity + getFilterPathsQuery(), + [&, this](const MetricsTasks& tasks) { - for (const model::CppFunctionBumpyRoad& function - : _ctx.db->query()) + util::OdbTransaction {_ctx.db} ([&, this] { - // Skip functions that were included from external libraries. - if (!cc::util::isRootedUnderAnyOf(_inputPaths, function.filePath)) - continue; - - const double dB = function.bumpiness; - const double dC = function.statementCount; - const bool empty = function.statementCount == 0; - - model::CppAstNodeMetrics metrics; - metrics.astNodeId = function.astNodeId; - metrics.type = model::CppAstNodeMetrics::Type::BUMPY_ROAD; - metrics.value = empty ? 1.0 : (dB / dC); - _ctx.db->persist(metrics); - } + for (const model::CppFunctionBumpyRoad& function : tasks) + { + const double dB = function.bumpiness; + const double dC = function.statementCount; + const bool empty = function.statementCount == 0; + + model::CppAstNodeMetrics metrics; + metrics.astNodeId = function.astNodeId; + metrics.type = model::CppAstNodeMetrics::Type::BUMPY_ROAD; + metrics.value = empty ? 1.0 : (dB / dC); + _ctx.db->persist(metrics); + } + }); }); } From 958d418e2861963e68475189fea64b54bd80f9ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Cser=C3=A9p?= Date: Tue, 18 Jun 2024 00:37:56 +0200 Subject: [PATCH 6/7] Move getFilterPathsQuery() to dbutil. --- .../cppmetricsparser/cppmetricsparser.h | 30 ++----------------- util/include/util/dbutil.h | 27 +++++++++++++++++ 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index ae37913e2..b001535c1 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -22,33 +23,6 @@ namespace cc namespace parser { -/// @brief Constructs an ODB query that you can use to filter only -/// the database records of the given parameter type whose path -/// is rooted under any of the specified filter paths. -/// @tparam TQueryParam The type of database records to query. -/// This type must represent an ODB view that has access to -/// (i.e. is also joined with) the File table. -/// @tparam TIter The iterator type of the filter paths. -/// @tparam TSentinel The type of the end of the filter paths. -/// @param begin_ The iterator referring to the first filter path. -/// @param end_ The sentinel for the end of the filter paths. -/// @return A query containing the disjunction of filters. -template -odb::query getFilterPathsQuery( - TIter begin_, - const TSentinel& end_) -{ - typedef typename odb::query::query_columns QParam; - const auto& QParamPath = QParam::File::path; - constexpr char ODBWildcard = '%'; - - assert(begin_ != end_ && "At least one filter path must be provided."); - odb::query query = QParamPath.like(*begin_ + ODBWildcard); - while (++begin_ != end_) - query = query || QParamPath.like(*begin_ + ODBWildcard); - return query; -} - template class MetricsTasks { @@ -107,7 +81,7 @@ class CppMetricsParser : public AbstractParser template odb::query getFilterPathsQuery() const { - return cc::parser::getFilterPathsQuery( + return cc::util::getFilterPathsQuery( _inputPaths.begin(), _inputPaths.end()); } diff --git a/util/include/util/dbutil.h b/util/include/util/dbutil.h index 5d1c331d5..e48ddae30 100644 --- a/util/include/util/dbutil.h +++ b/util/include/util/dbutil.h @@ -131,6 +131,33 @@ bool isSingleResult(odb::result& result_) return (it_b != it_e) && (++it_b == it_e); } +/// @brief Constructs an ODB query that you can use to filter only +/// the database records of the given parameter type whose path +/// is rooted under any of the specified filter paths. +/// @tparam TQueryParam The type of database records to query. +/// This type must represent an ODB view that has access to +/// (i.e. is also joined with) the File table. +/// @tparam TIter The iterator type of the filter paths. +/// @tparam TSentinel The type of the end of the filter paths. +/// @param begin_ The iterator referring to the first filter path. +/// @param end_ The sentinel for the end of the filter paths. +/// @return A query containing the disjunction of filters. +template +odb::query getFilterPathsQuery( + TIter begin_, + const TSentinel& end_) +{ + typedef typename odb::query::query_columns QParam; + const auto& QParamPath = QParam::File::path; + constexpr char ODBWildcard = '%'; + + assert(begin_ != end_ && "At least one filter path must be provided."); + odb::query query = QParamPath.like(*begin_ + ODBWildcard); + while (++begin_ != end_) + query = query || QParamPath.like(*begin_ + ODBWildcard); + return query; +} + } // util } // cc From fc86a64c0fa4e4299814b9d48a98378c00522309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Cser=C3=A9p?= Date: Tue, 18 Jun 2024 00:45:52 +0200 Subject: [PATCH 7/7] Extract magic numbers to const members. --- .../parser/include/cppmetricsparser/cppmetricsparser.h | 5 +++++ plugins/cpp_metrics/parser/src/cppmetricsparser.cpp | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index b001535c1..574e233df 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -185,6 +185,11 @@ class CppMetricsParser : public AbstractParser std::vector _inputPaths; std::unordered_set _fileIdCache; std::unordered_map _astNodeIdCache; + + static const int functionParamsPartitionMultiplier = 5; + static const int functionMcCabePartitionMultiplier = 5; + static const int functionBumpyRoadPartitionMultiplier = 5; + static const int lackOfCohesionPartitionMultiplier = 25; }; } // parser diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index dcb3ef77c..92561e1db 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -102,7 +102,7 @@ void CppMetricsParser::functionParameters() { parallelCalcMetric( "Function parameters", - _threadCount * 5,// number of jobs; adjust for granularity + _threadCount * functionParamsPartitionMultiplier,// number of jobs; adjust for granularity getFilterPathsQuery(), [&, this](const MetricsTasks& tasks) { @@ -124,7 +124,7 @@ void CppMetricsParser::functionMcCabe() { parallelCalcMetric( "Function-level McCabe", - _threadCount * 5,// number of jobs; adjust for granularity + _threadCount * functionMcCabePartitionMultiplier,// number of jobs; adjust for granularity getFilterPathsQuery(), [&, this](const MetricsTasks& tasks) { @@ -147,7 +147,7 @@ void CppMetricsParser::functionBumpyRoad() // Calculate the bumpy road metric for all types on parallel threads. parallelCalcMetric( "Bumpy road complexity", - _threadCount * 5,// number of jobs; adjust for granularity + _threadCount * functionBumpyRoadPartitionMultiplier,// number of jobs; adjust for granularity getFilterPathsQuery(), [&, this](const MetricsTasks& tasks) { @@ -174,7 +174,7 @@ void CppMetricsParser::lackOfCohesion() // Calculate the cohesion metric for all types on parallel threads. parallelCalcMetric( "Lack of cohesion", - _threadCount * 25,// number of jobs; adjust for granularity + _threadCount * lackOfCohesionPartitionMultiplier, // number of jobs; adjust for granularity getFilterPathsQuery(), [&, this](const MetricsTasks& tasks) {