Added thread pool to parallelize diagram generation

This commit is contained in:
Bartek Kryza
2022-03-18 22:55:15 +01:00
parent 82737df05c
commit c49969495f
14 changed files with 143 additions and 161 deletions

View File

@@ -179,8 +179,8 @@ void generator::generate(
}
}
catch (error::uml_alias_missing &e) {
LOG_ERROR("=== Skipping {} relation from {} to {} due "
"to: {}",
LOG_DBG("=== Skipping {} relation from {} to {} due "
"to: {}",
plantuml_common::to_plantuml(r.type(), r.style()),
c.full_name(), destination, e.what());
}
@@ -215,8 +215,8 @@ void generator::generate(
all_relations_str << relstr.str();
}
catch (error::uml_alias_missing &e) {
LOG_ERROR("=== Skipping inheritance relation from {} to {} due "
"to: {}",
LOG_DBG("=== Skipping inheritance relation from {} to {} due "
"to: {}",
b.name(), c.name(), e.what());
}
}
@@ -271,8 +271,8 @@ void generator::generate(
relationships_ostr << relstr.str();
}
catch (error::uml_alias_missing &ex) {
LOG_ERROR("Skipping {} relation from {} to {} due "
"to: {}",
LOG_DBG("Skipping {} relation from {} to {} due "
"to: {}",
clanguml::common::generators::plantuml::to_plantuml(
r.type(), r.style()),
e.full_name(), destination, ex.what());

View File

@@ -200,7 +200,7 @@ void translation_unit_visitor::process_type_alias_template(
{
if (at.type_alias().underlying_type().kind() ==
cppast::cpp_type_kind::unexposed_t) {
LOG_WARN("Template alias has unexposed underlying type: {}",
LOG_DBG("Template alias has unexposed underlying type: {}",
static_cast<const cppast::cpp_unexposed_type &>(
at.type_alias().underlying_type())
.name());
@@ -407,8 +407,8 @@ bool translation_unit_visitor::process_template_parameters(
process_scope_template_parameters(c, scope);
}
else {
LOG_WARN("Class {} is templated but it's scope {} is not - "
"probably this is a specialization",
LOG_DBG("Class {} is templated but it's scope {} is not - "
"probably this is a specialization",
cls.name(), scope.name());
// Add specialization arguments
@@ -541,7 +541,7 @@ void translation_unit_visitor::
{relationship_t::kInstantiation, base_template_full_name});
}
else {
LOG_WARN(
LOG_DBG(
"No user data for base template {}", primary_template_ref.name());
}
}
@@ -1321,8 +1321,7 @@ bool translation_unit_visitor::find_relationships_in_template_instantiation(
found = find_relationships(args[0u].type().value(), relationships,
relationship_t::kAggregation);
else
LOG_WARN(
"Failed to process template argument of std::vector at: {}",
LOG_DBG("Failed to process template argument of std::vector at: {}",
fn);
}
else if (ctx.config().should_include(ns, name)) {
@@ -1470,7 +1469,7 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
t, tinst, template_base_params, parent, full_template_name);
}
else {
LOG_WARN("Template instantiation {} has no primary template",
LOG_DBG("Template instantiation {} has no primary template",
cppast::to_string(t));
full_template_name = cppast::to_string(t);
@@ -1769,7 +1768,7 @@ void translation_unit_visitor::build_template_instantiation_primary_template(
LOG_DBG("Primary template ref set to: {}", tinst.base_template());
}
else
LOG_WARN(
LOG_DBG(
"No user data for base template {}", primary_template_ref.name());
}

View File

@@ -90,8 +90,8 @@ void generator<C, D>::generate_config_layout_hints(std::ostream &ostr) const
ostr << hint_str.str();
}
catch (clanguml::error::uml_alias_missing &e) {
LOG_ERROR("=== Skipping layout hint from {} to {} due "
"to: {}",
LOG_DBG("=== Skipping layout hint from {} to {} due "
"to: {}",
entity, hint.entity, e.what());
}
}
@@ -133,8 +133,8 @@ void generator<C, D>::generate_notes(
template <typename DiagramModel, typename DiagramConfig,
typename DiagramVisitor>
DiagramModel generate(cppast::libclang_compilation_database &db,
const std::string &name, DiagramConfig &diagram)
DiagramModel generate(const cppast::libclang_compilation_database &db,
const std::string &name, DiagramConfig &diagram, bool verbose = false)
{
LOG_INFO("Generating diagram {}.puml", name);
DiagramModel d;
@@ -151,8 +151,10 @@ DiagramModel generate(cppast::libclang_compilation_database &db,
}
cppast::cpp_entity_index idx;
auto logger =
verbose ? cppast::default_logger() : cppast::default_quiet_logger();
cppast::simple_file_parser<cppast::libclang_parser> parser{
type_safe::ref(idx)};
type_safe::ref(idx), std::move(logger)};
// Process all matching translation units
DiagramVisitor ctx(idx, d, diagram);

View File

@@ -37,15 +37,15 @@ std::string element::alias() const { return fmt::format("C_{:010}", m_id); }
void element::add_relationship(relationship &&cr)
{
if (cr.destination().empty()) {
LOG_WARN("Skipping relationship '{}' - {} - '{}' due empty "
"destination",
LOG_DBG("Skipping relationship '{}' - {} - '{}' due empty "
"destination",
cr.destination(), to_string(cr.type()), full_name(true));
return;
}
if ((cr.type() == relationship_t::kInstantiation) &&
(cr.destination() == full_name(true))) {
LOG_WARN("Skipping self instantiation relationship for {}",
LOG_DBG("Skipping self instantiation relationship for {}",
cr.destination());
return;
}

View File

@@ -81,8 +81,7 @@ public:
LOG_DBG("Getting nested element at path: {}", path.to_string());
if (path.is_empty() || !has_element(path[0])) {
LOG_WARN(
"Nested element {} not found in element", path.to_string());
LOG_DBG("Nested element {} not found in element", path.to_string());
return type_safe::optional_ref<V>{};
}

View File

@@ -135,13 +135,6 @@ bool diagram::should_include(
return should_include(std::get<0>(name), std::get<1>(name));
}
// bool diagram::should_include(
// const common::model::namespace_ &ns, const std::string &name) const
//{
// auto ns_and_name = ns | name;
// return should_include(ns_and_name.to_string());
//}
bool diagram::should_include(
const common::model::namespace_ &ns, const std::string &name) const
{
@@ -346,7 +339,7 @@ std::shared_ptr<clanguml::config::diagram> parse_diagram_config(const Node &d)
return std::make_shared<package_diagram>(d.as<package_diagram>());
}
LOG_WARN("Diagrams of type {} are not supported... ", diagram_type);
LOG_ERROR("Diagrams of type {} are not supported... ", diagram_type);
return {};
}

View File

@@ -110,10 +110,6 @@ struct diagram : public inheritable_diagram_options {
bool should_include(
const std::pair<common::model::namespace_, std::string> &name) const;
// bool should_include(
// const std::vector<std::string> &ns, const std::string &name)
// const;
bool should_include(
const common::model::namespace_ &ns, const std::string &name) const;

View File

@@ -16,25 +16,25 @@
* limitations under the License.
*/
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG
#include "class_diagram/generators/plantuml/class_diagram_generator.h"
#include "config/config.h"
#include "cx/compilation_database.h"
#include "package_diagram/generators/plantuml/package_diagram_generator.h"
#include "sequence_diagram/generators/plantuml/sequence_diagram_generator.h"
#include "config/config.h"
#include "util/util.h"
#include <cli11/CLI11.hpp>
#include <cppast/libclang_parser.hpp>
#include <spdlog/cfg/env.h>
#include <spdlog/spdlog.h>
#include <filesystem>
#include <fstream>
#include <future>
#include <iostream>
#include <string.h>
#include <util/thread_pool_executor.h>
using namespace clanguml;
using config::config;
@@ -44,6 +44,10 @@ void print_diagrams_list(const clanguml::config::config &cfg);
bool check_output_directory(const std::string &dir);
void generate_diagram(const std::string &od, const std::string &name,
std::shared_ptr<clanguml::config::diagram> diagram,
const cppast::libclang_compilation_database &db, bool verbose);
int main(int argc, const char *argv[])
{
CLI::App app{"Clang-based PlantUML diagram generator for C++"};
@@ -52,6 +56,7 @@ int main(int argc, const char *argv[])
std::string compilation_database_dir{'.'};
std::vector<std::string> diagram_names{};
std::optional<std::string> output_directory;
unsigned int thread_count{0};
bool verbose{false};
bool list_diagrams{false};
@@ -63,16 +68,15 @@ int main(int argc, const char *argv[])
"List of diagram names to generate");
app.add_option("-o,--output-directory", output_directory,
"Override output directory specified in config file");
app.add_option("-t,--thread-count", thread_count,
"Thread pool size (0 = hardware concurrency)");
app.add_flag("-v,--verbose", verbose, "Verbose logging");
app.add_flag("-l,--list-diagrams", list_diagrams,
"Print list of diagrams defined in the config file");
CLI11_PARSE(app, argc, argv);
if (verbose) {
spdlog::default_logger_raw()->set_level(spdlog::level::debug);
spdlog::default_logger_raw()->set_pattern("[%l] %v");
}
clanguml::util::setup_logging(verbose);
clanguml::config::config config;
try {
@@ -93,7 +97,7 @@ int main(int argc, const char *argv[])
LOG_INFO("Loading compilation database from {} directory",
config.compilation_database_dir());
cppast::libclang_compilation_database db(config.compilation_database_dir());
cppast::libclang_compilation_database db{config.compilation_database_dir()};
auto od = config.output_directory();
if (output_directory)
@@ -102,73 +106,90 @@ int main(int argc, const char *argv[])
if (!check_output_directory(od))
return 1;
util::thread_pool_executor generator_executor{thread_count};
std::vector<std::future<void>> futs;
for (const auto &[name, diagram] : config.diagrams) {
// If there are any specific diagram names provided on the command line,
// and this diagram is not in that list - skip it
if (!diagram_names.empty() && !util::contains(diagram_names, name))
continue;
using clanguml::config::class_diagram;
using clanguml::config::diagram_type;
using clanguml::config::package_diagram;
using clanguml::config::sequence_diagram;
futs.emplace_back(generator_executor.add(
[&od, &name = name, &diagram = diagram, &db = db, verbose]() {
generate_diagram(od, name, diagram, db, verbose);
}));
}
auto path = std::filesystem::path{od} / fmt::format("{}.puml", name);
std::ofstream ofs;
ofs.open(path, std::ofstream::out | std::ofstream::trunc);
if (diagram->type() == diagram_type::class_diagram) {
using diagram_config = clanguml::config::class_diagram;
using diagram_model = clanguml::class_diagram::model::diagram;
using diagram_visitor =
clanguml::class_diagram::visitor::translation_unit_visitor;
auto model =
clanguml::common::generators::plantuml::generate<diagram_model,
diagram_config, diagram_visitor>(db, diagram->name,
dynamic_cast<diagram_config &>(*diagram));
ofs << clanguml::class_diagram::generators::plantuml::generator(
dynamic_cast<diagram_config &>(*diagram), model);
}
else if (diagram->type() == diagram_type::sequence_diagram) {
using diagram_config = clanguml::config::sequence_diagram;
using diagram_model = clanguml::sequence_diagram::model::diagram;
using diagram_visitor =
clanguml::sequence_diagram::visitor::translation_unit_visitor;
auto model =
clanguml::common::generators::plantuml::generate<diagram_model,
diagram_config, diagram_visitor>(db, diagram->name,
dynamic_cast<diagram_config &>(*diagram));
ofs << clanguml::sequence_diagram::generators::plantuml::generator(
dynamic_cast<clanguml::config::sequence_diagram &>(*diagram),
model);
}
else if (diagram->type() == diagram_type::package_diagram) {
using diagram_config = clanguml::config::package_diagram;
using diagram_model = clanguml::package_diagram::model::diagram;
using diagram_visitor =
clanguml::package_diagram::visitor::translation_unit_visitor;
auto model =
clanguml::common::generators::plantuml::generate<diagram_model,
diagram_config, diagram_visitor>(db, diagram->name,
dynamic_cast<diagram_config &>(*diagram));
ofs << clanguml::package_diagram::generators::plantuml::generator(
dynamic_cast<diagram_config &>(*diagram), model);
}
LOG_INFO("Written {} diagram to {}", name, path.string());
ofs.close();
for (auto &fut : futs) {
fut.get();
}
return 0;
}
void generate_diagram(const std::string &od, const std::string &name,
std::shared_ptr<clanguml::config::diagram> diagram,
const cppast::libclang_compilation_database &db, bool verbose)
{
using clanguml::config::class_diagram;
using clanguml::config::diagram_type;
using clanguml::config::package_diagram;
using clanguml::config::sequence_diagram;
auto path = std::filesystem::path{od} / fmt::format("{}.puml", name);
std::ofstream ofs;
ofs.open(path, std::ofstream::out | std::ofstream::trunc);
if (diagram->type() == diagram_type::class_diagram) {
using diagram_config = class_diagram;
using diagram_model = clanguml::class_diagram::model::diagram;
using diagram_visitor =
clanguml::class_diagram::visitor::translation_unit_visitor;
auto model =
clanguml::common::generators::plantuml::generate<diagram_model,
diagram_config, diagram_visitor>(db, diagram->name,
dynamic_cast<diagram_config &>(*diagram), verbose);
ofs << clanguml::class_diagram::generators::plantuml::generator(
dynamic_cast<diagram_config &>(*diagram), model);
}
else if (diagram->type() == diagram_type::sequence_diagram) {
using diagram_config = sequence_diagram;
using diagram_model = clanguml::sequence_diagram::model::diagram;
using diagram_visitor =
clanguml::sequence_diagram::visitor::translation_unit_visitor;
auto model =
clanguml::common::generators::plantuml::generate<diagram_model,
diagram_config, diagram_visitor>(db, diagram->name,
dynamic_cast<diagram_config &>(*diagram), verbose);
ofs << clanguml::sequence_diagram::generators::plantuml::generator(
dynamic_cast<clanguml::config::sequence_diagram &>(*diagram),
model);
}
else if (diagram->type() == diagram_type::package_diagram) {
using diagram_config = package_diagram;
using diagram_model = clanguml::package_diagram::model::diagram;
using diagram_visitor =
clanguml::package_diagram::visitor::translation_unit_visitor;
auto model =
clanguml::common::generators::plantuml::generate<diagram_model,
diagram_config, diagram_visitor>(db, diagram->name,
dynamic_cast<diagram_config &>(*diagram), verbose);
ofs << clanguml::package_diagram::generators::plantuml::generator(
dynamic_cast<diagram_config &>(*diagram), model);
}
LOG_INFO("Written {} diagram to {}", name, path.string());
ofs.close();
}
bool check_output_directory(const std::string &dir)
{
namespace fs = std::filesystem;

View File

@@ -46,8 +46,8 @@ void generator::generate_relationships(
ostr << relstr.str();
}
catch (error::uml_alias_missing &e) {
LOG_ERROR("=== Skipping dependency relation from {} to {} due "
"to: {}",
LOG_DBG("=== Skipping dependency relation from {} to {} due "
"to: {}",
p.full_name(false), r.destination(), e.what());
}
}

View File

@@ -35,7 +35,7 @@ thread_pool_executor::thread_pool_executor(unsigned int pool_size)
}
}
~thread_pool_executor::thread_pool_executor() { stop(); }
thread_pool_executor::~thread_pool_executor() { stop(); }
std::future<void> thread_pool_executor::add(std::function<void()> &&task)
{

View File

@@ -26,6 +26,18 @@ namespace util {
const std::string WHITESPACE = " \n\r\t\f\v";
void setup_logging(bool verbose)
{
auto console =
spdlog::stdout_color_mt("console", spdlog::color_mode::automatic);
console->set_pattern("[%^%l%^] [tid %t] %v");
if (verbose) {
console->set_level(spdlog::level::debug);
}
}
std::string ltrim(const std::string &s)
{
size_t start = s.find_first_not_of(WHITESPACE);
@@ -68,34 +80,6 @@ std::string join(const std::vector<std::string> &toks, std::string delimiter)
return fmt::format("{}", fmt::join(toks, delimiter));
}
/*
std::string ns_relative(
const std::vector<std::string> &namespaces, const std::string &n)
{
std::vector<std::string> namespaces_sorted{namespaces};
std::sort(namespaces_sorted.rbegin(), namespaces_sorted.rend());
auto res = n;
for (const auto &ns : namespaces_sorted) {
if (ns.empty())
continue;
if (n == ns)
return split(n, "::").back();
auto ns_prefix = ns + "::";
auto it = res.find(ns_prefix);
while (it != std::string::npos) {
res.erase(it, ns_prefix.size());
it = res.find(ns_prefix);
}
}
return res;
}
*/
std::string unqualify(const std::string &s)
{
auto toks = clanguml::util::split(s, " ");

View File

@@ -17,6 +17,7 @@
*/
#pragma once
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <algorithm>
@@ -36,20 +37,27 @@ std::string trim(const std::string &s);
(strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define LOG_ERROR(fmt__, ...) \
spdlog::error(std::string("[{}:{}] ") + fmt__, __FILENAME__, __LINE__, \
##__VA_ARGS__)
spdlog::get("console")->error(std::string("[{}:{}] ") + fmt__, \
__FILENAME__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(fmt__, ...) \
spdlog::warn(std::string("[{}:{}] ") + fmt__, __FILENAME__, __LINE__, \
##__VA_ARGS__)
spdlog::get("console")->warn(std::string("[{}:{}] ") + fmt__, \
__FILENAME__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(fmt__, ...) \
spdlog::info(std::string("[{}:{}] ") + fmt__, __FILENAME__, __LINE__, \
##__VA_ARGS__)
spdlog::get("console")->info(std::string("[{}:{}] ") + fmt__, \
__FILENAME__, __LINE__, ##__VA_ARGS__)
#define LOG_DBG(fmt__, ...) \
spdlog::debug(std::string("[{}:{}] ") + fmt__, __FILENAME__, __LINE__, \
##__VA_ARGS__)
spdlog::get("console")->debug(std::string("[{}:{}] ") + fmt__, \
__FILENAME__, __LINE__, ##__VA_ARGS__)
/**
* @brief Setup spdlog logger.
*
* @param verbose Whether the logging should be verbose or not.
*/
void setup_logging(bool verbose);
/**
* @brief Split a string using delimiter
@@ -67,25 +75,6 @@ std::vector<std::string> split(std::string str, std::string delimiter);
std::string join(const std::vector<std::string> &toks, std::string delimiter);
/**
* @brief Get name of the identifier relative to a set of namespaces
*
* This function tries to match a given C++ identifier (e.g.
* clanguml::util::split) to the longest namespace from the provided list
* matching the identifier from the left.
* If a match is found, the relative identifier is returned. If none of
* the namespaces match the identifier or if nothing is left after
* removing the matching namespace from the identifier, original identifier is
* returned.
*
* @param namespaces List of C++ namespaces to consider
* @param n Identifier to relativize
*
* @return Identifier relative to one of the matching namespaces.
*/
// std::string ns_relative(
// const std::vector<std::string> &namespaces, const std::string &n);
/**
* @brief Remove any qualifiers (e.g. const) from type.
*