Added --progress option for diagram progress indicators

This commit is contained in:
Bartek Kryza
2023-06-15 00:24:40 +02:00
parent c387aba82e
commit 408416020f
7 changed files with 289 additions and 29 deletions

View File

@@ -26,6 +26,7 @@
#include <clang/Basic/Version.h>
#include <clang/Config/config.h>
#include <indicators/indicators.hpp>
namespace clanguml::cli {
cli_handler::cli_handler(
@@ -38,7 +39,17 @@ cli_handler::cli_handler(
void cli_handler::setup_logging()
{
spdlog::drop("clanguml-logger");
if (!progress) {
spdlog::register_logger(logger_);
}
else {
// Setup null logger for clean progress indicators
std::vector<spdlog::sink_ptr> sinks;
logger_ = std::make_shared<spdlog::logger>(
"clanguml-logger", begin(sinks), end(sinks));
spdlog::register_logger(logger_);
}
logger_->set_pattern("[%^%l%^] [tid %t] %v");
@@ -79,6 +90,8 @@ cli_flow_t cli_handler::parse(int argc, const char **argv)
app.add_flag("-V,--version", show_version, "Print version and exit");
app.add_flag("-v,--verbose", verbose,
"Verbose logging (use multiple times to increase - e.g. -vvv)");
app.add_flag(
"-p,--progress", progress, "Show progress bars for generated diagrams");
app.add_flag("-q,--quiet", quiet, "Minimal logging");
app.add_flag("-l,--list-diagrams", list_diagrams,
"Print list of diagrams defined in the config file");
@@ -135,6 +148,9 @@ cli_flow_t cli_handler::parse(int argc, const char **argv)
else
verbose++;
if (progress)
verbose = 0;
return cli_flow_t::kContinue;
}

View File

@@ -118,6 +118,7 @@ public:
unsigned int thread_count{};
bool show_version{false};
int verbose{};
bool progress{false};
bool list_diagrams{false};
bool quiet{false};
bool initialize{false};

View File

@@ -18,6 +18,8 @@
#include "generators.h"
#include "progress_indicator.h"
namespace clanguml::common::generators {
void find_translation_units_for_diagrams(
const std::vector<std::string> &diagram_names,
@@ -82,7 +84,7 @@ void generate_diagram_impl(const std::string &od, const std::string &name,
const common::compilation_database &db,
const std::vector<std::string> &translation_units,
const std::vector<clanguml::common::generator_type_t> &generators,
bool verbose)
bool verbose, std::function<void()> &&progress)
{
using diagram_config = DiagramConfig;
using diagram_model = typename diagram_model_t<DiagramConfig>::type;
@@ -90,7 +92,8 @@ void generate_diagram_impl(const std::string &od, const std::string &name,
auto model = clanguml::common::generators::generate<diagram_model,
diagram_config, diagram_visitor>(db, diagram->name,
dynamic_cast<diagram_config &>(*diagram), translation_units, verbose);
dynamic_cast<diagram_config &>(*diagram), translation_units, verbose,
std::move(progress));
for (const auto generator_type : generators) {
if (generator_type == generator_type_t::plantuml) {
@@ -110,7 +113,7 @@ void generate_diagram(const std::string &od, const std::string &name,
const common::compilation_database &db,
const std::vector<std::string> &translation_units,
const std::vector<clanguml::common::generator_type_t> &generators,
bool verbose)
bool verbose, std::function<void()> &&progress)
{
using clanguml::common::generator_type_t;
using clanguml::common::model::diagram_t;
@@ -121,27 +124,27 @@ void generate_diagram(const std::string &od, const std::string &name,
using clanguml::config::sequence_diagram;
if (diagram->type() == diagram_t::kClass) {
detail::generate_diagram_impl<class_diagram>(
od, name, diagram, db, translation_units, generators, verbose);
detail::generate_diagram_impl<class_diagram>(od, name, diagram, db,
translation_units, generators, verbose, std::move(progress));
}
else if (diagram->type() == diagram_t::kSequence) {
detail::generate_diagram_impl<sequence_diagram>(
od, name, diagram, db, translation_units, generators, verbose);
detail::generate_diagram_impl<sequence_diagram>(od, name, diagram, db,
translation_units, generators, verbose, std::move(progress));
}
else if (diagram->type() == diagram_t::kPackage) {
detail::generate_diagram_impl<package_diagram>(
od, name, diagram, db, translation_units, generators, verbose);
detail::generate_diagram_impl<package_diagram>(od, name, diagram, db,
translation_units, generators, verbose, std::move(progress));
}
else if (diagram->type() == diagram_t::kInclude) {
detail::generate_diagram_impl<include_diagram>(
od, name, diagram, db, translation_units, generators, verbose);
detail::generate_diagram_impl<include_diagram>(od, name, diagram, db,
translation_units, generators, verbose, std::move(progress));
}
}
void generate_diagrams(const std::vector<std::string> &diagram_names,
config::config &config, const std::string &od,
const common::compilation_database_ptr &db, const int verbose,
const unsigned int thread_count,
const unsigned int thread_count, bool progress,
const std::vector<clanguml::common::generator_type_t> &generators,
const std::map<std::string, std::vector<std::string>>
&translation_units_map)
@@ -149,6 +152,14 @@ void generate_diagrams(const std::vector<std::string> &diagram_names,
util::thread_pool_executor generator_executor{thread_count};
std::vector<std::future<void>> futs;
std::unique_ptr<progress_indicator> indicator;
if (progress) {
std::cout << termcolor::white
<< "Processing translation units and generating diagrams:\n";
indicator = std::make_unique<progress_indicator>();
}
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
@@ -158,22 +169,44 @@ void generate_diagrams(const std::vector<std::string> &diagram_names,
const auto &valid_translation_units = translation_units_map.at(name);
if (valid_translation_units.empty()) {
LOG_ERROR("Diagram {} generation failed: no translation units "
if (indicator) {
indicator->add_progress_bar(
name, 0, diagram_type_to_color(diagram->type()));
indicator->fail(name);
}
else {
LOG_ERROR(
"Diagram {} generation failed: no translation units "
"found. Please make sure that your 'glob' patterns match "
"at least 1 file in 'compile_commands.json'.",
name);
}
continue;
}
futs.emplace_back(generator_executor.add(
[&od, &generators, &name = name, &diagram = diagram,
[&od, &generators, &name = name, &diagram = diagram, &indicator,
db = std::ref(*db), translation_units = valid_translation_units,
verbose]() {
verbose]() mutable {
try {
if (indicator)
indicator->add_progress_bar(name,
translation_units.size(),
diagram_type_to_color(diagram->type()));
generate_diagram(od, name, diagram, db, translation_units,
generators, verbose != 0);
generators, verbose != 0, [&indicator, &name]() {
if (indicator)
indicator->increment(name);
});
if (indicator)
indicator->complete(name);
}
catch (std::runtime_error &e) {
if (indicator)
indicator->fail(name);
LOG_ERROR(e.what());
}
}));
@@ -182,6 +215,28 @@ void generate_diagrams(const std::vector<std::string> &diagram_names,
for (auto &fut : futs) {
fut.get();
}
if (progress) {
indicator->stop();
std::cout << termcolor::white << "Done\n";
std::cout << termcolor::reset;
}
}
indicators::Color diagram_type_to_color(model::diagram_t diagram_type)
{
switch (diagram_type) {
case model::diagram_t::kClass:
return indicators::Color::yellow;
case model::diagram_t::kSequence:
return indicators::Color::blue;
case model::diagram_t::kPackage:
return indicators::Color::cyan;
case model::diagram_t::kInclude:
return indicators::Color::magenta;
default:
return indicators::Color::unspecified;
}
}
} // namespace clanguml::common::generators

View File

@@ -26,6 +26,7 @@
#include "config/config.h"
#include "include_diagram/generators/json/include_diagram_generator.h"
#include "include_diagram/generators/plantuml/include_diagram_generator.h"
#include "indicators/indicators.hpp"
#include "package_diagram/generators/json/package_diagram_generator.h"
#include "package_diagram/generators/plantuml/package_diagram_generator.h"
#include "sequence_diagram/generators/json/sequence_diagram_generator.h"
@@ -164,10 +165,11 @@ template <typename DiagramModel, typename DiagramConfig,
typename DiagramVisitor>
class diagram_fronted_action : public clang::ASTFrontendAction {
public:
explicit diagram_fronted_action(
DiagramModel &diagram, const DiagramConfig &config)
explicit diagram_fronted_action(DiagramModel &diagram,
const DiagramConfig &config, std::function<void()> progress)
: diagram_{diagram}
, config_{config}
, progress_{std::move(progress)}
{
}
@@ -191,6 +193,9 @@ protected:
{
LOG_DBG("Visiting source file: {}", getCurrentFile().str());
if (progress_)
progress_();
if constexpr (std::is_same_v<DiagramModel,
clanguml::include_diagram::model::diagram>) {
auto find_includes_callback =
@@ -208,6 +213,7 @@ protected:
private:
DiagramModel &diagram_;
const DiagramConfig &config_;
std::function<void()> progress_;
};
template <typename DiagramModel, typename DiagramConfig,
@@ -215,29 +221,32 @@ template <typename DiagramModel, typename DiagramConfig,
class diagram_action_visitor_factory
: public clang::tooling::FrontendActionFactory {
public:
explicit diagram_action_visitor_factory(
DiagramModel &diagram, const DiagramConfig &config)
explicit diagram_action_visitor_factory(DiagramModel &diagram,
const DiagramConfig &config, std::function<void()> progress)
: diagram_{diagram}
, config_{config}
, progress_{progress}
{
}
std::unique_ptr<clang::FrontendAction> create() override
{
return std::make_unique<diagram_fronted_action<DiagramModel,
DiagramConfig, DiagramVisitor>>(diagram_, config_);
DiagramConfig, DiagramVisitor>>(diagram_, config_, progress_);
}
private:
DiagramModel &diagram_;
const DiagramConfig &config_;
std::function<void()> progress_;
};
template <typename DiagramModel, typename DiagramConfig,
typename DiagramVisitor>
std::unique_ptr<DiagramModel> generate(const common::compilation_database &db,
const std::string &name, DiagramConfig &config,
const std::vector<std::string> &translation_units, bool /*verbose*/ = false)
const std::vector<std::string> &translation_units, bool /*verbose*/ = false,
std::function<void()> progress = {})
{
LOG_INFO("Generating diagram {}", name);
@@ -252,7 +261,8 @@ std::unique_ptr<DiagramModel> generate(const common::compilation_database &db,
clang::tooling::ClangTool clang_tool(db, translation_units);
auto action_factory =
std::make_unique<diagram_action_visitor_factory<DiagramModel,
DiagramConfig, DiagramVisitor>>(*diagram, config);
DiagramConfig, DiagramVisitor>>(
*diagram, config, std::move(progress));
auto res = clang_tool.run(action_factory.get());
@@ -275,9 +285,11 @@ void generate_diagram(const std::string &od, const std::string &name,
void generate_diagrams(const std::vector<std::string> &diagram_names,
clanguml::config::config &config, const std::string &od,
const common::compilation_database_ptr &db, int verbose,
unsigned int thread_count,
unsigned int thread_count, bool progress,
const std::vector<clanguml::common::generator_type_t> &generators,
const std::map<std::string, std::vector<std::string>>
&translation_units_map);
indicators::Color diagram_type_to_color(model::diagram_t diagram_type);
} // namespace clanguml::common::generators

View File

@@ -0,0 +1,121 @@
/**
* src/common/generators/progress_indicator.cc
*
* Copyright (c) 2021-2023 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "progress_indicator.h"
#include <util/util.h>
namespace clanguml::common::generators {
progress_indicator::progress_indicator()
{
progress_bars_.set_option(indicators::option::HideBarWhenComplete{false});
}
void progress_indicator::add_progress_bar(
const std::string &name, size_t max, indicators::Color color)
{
auto postfix_text = max > 0 ? fmt::format("{}/{}", 0, max) : std::string{};
auto bar = std::make_shared<indicators::ProgressBar>(
indicators::option::BarWidth{34},
indicators::option::ForegroundColor{color},
indicators::option::ShowElapsedTime{true},
indicators::option::Fill{""}, indicators::option::Lead{""},
indicators::option::Remainder{"-"},
indicators::option::PrefixText{
fmt::format("{:<25}", util::abbreviate(name, 25))},
indicators::option::PostfixText{postfix_text});
progress_bars_mutex_.lock();
progress_bars_.push_back(*bar);
bars_.push_back(bar);
auto bar_index = bars_.size() - 1;
progress_bar_index_.emplace(
name, progress_state{.index = bar_index, .progress = 0, .max = max});
progress_bars_mutex_.unlock();
}
void progress_indicator::increment(const std::string &name)
{
progress_bars_mutex_.lock();
if (progress_bar_index_.count(name) == 0) {
progress_bars_mutex_.unlock();
return;
}
auto &p = progress_bar_index_.at(name);
auto &bar = progress_bars_[p.index];
progress_bars_mutex_.unlock();
p.progress++;
bar.set_progress((p.progress * 95) / p.max);
bar.set_option(indicators::option::PostfixText{
fmt::format("{}/{}", p.progress, p.max)});
}
void progress_indicator::stop()
{
progress_bars_mutex_.lock();
for (auto &[name, p] : progress_bar_index_) {
progress_bars_[p.index].mark_as_completed();
}
progress_bars_mutex_.unlock();
}
void progress_indicator::complete(const std::string &name)
{
progress_bars_mutex_.lock();
if (progress_bar_index_.count(name) == 0) {
progress_bars_mutex_.unlock();
return;
}
auto &p = progress_bar_index_.at(name);
auto &bar = progress_bars_[p.index];
progress_bars_mutex_.unlock();
bar.set_progress(100);
bar.set_option(indicators::option::PostfixText{
fmt::format("{}/{} ✔", p.progress, p.max)});
bar.set_option(
indicators::option::ForegroundColor{indicators::Color::green});
bar.mark_as_completed();
}
void progress_indicator::fail(const std::string &name)
{
progress_bars_mutex_.lock();
auto &p = progress_bar_index_.at(name);
auto &bar = progress_bars_[p.index];
progress_bars_mutex_.unlock();
bar.set_option(indicators::option::ForegroundColor{indicators::Color::red});
bar.set_option(indicators::option::PostfixText{
fmt::format("{}/{} ✗", p.progress, p.max)});
bar.mark_as_completed();
}
} // namespace clanguml::common::generators

View File

@@ -0,0 +1,55 @@
/**
* src/common/generators/progress_indicator.h
*
* Copyright (c) 2021-2023 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <indicators/indicators.hpp>
#include <map>
#include <memory>
#include <vector>
namespace clanguml::common::generators {
class progress_indicator {
public:
struct progress_state {
size_t index;
size_t progress;
size_t max;
};
progress_indicator();
void add_progress_bar(
const std::string &name, size_t max, indicators::Color color);
void increment(const std::string &name);
void stop();
void complete(const std::string &name);
void fail(const std::string &name);
private:
indicators::DynamicProgress<indicators::ProgressBar> progress_bars_;
std::vector<std::shared_ptr<indicators::ProgressBar>> bars_;
std::map<std::string, progress_state> progress_bar_index_;
std::mutex progress_bars_mutex_;
};
} // namespace clanguml::common::generators

View File

@@ -79,7 +79,7 @@ int main(int argc, const char *argv[])
common::generators::generate_diagrams(cli.diagram_names, cli.config,
cli.effective_output_directory, db, cli.verbose, cli.thread_count,
cli.generators, translation_units_map);
cli.progress, cli.generators, translation_units_map);
}
catch (common::compilation_database_error &e) {
LOG_ERROR("Failed to load compilation database from {} due to: {}",