Merge pull request #159 from bkryza/add-progress-indicators

Add progress indicators
This commit is contained in:
Bartek Kryza
2023-06-15 22:54:14 +02:00
committed by GitHub
11 changed files with 3570 additions and 37 deletions

View File

@@ -18,6 +18,10 @@ The diagrams can be generated in [PlantUML](https://plantuml.com) and JSON forma
Full documentation can be found [here](./docs/README.md).
To see what `clang-uml` can do, checkout the diagrams generated for unit
test cases [here](./docs/test_cases.md) or examples in
[clang-uml-examples](https://github.com/bkryza/clang-uml-examples) repository.
## Features
Main features supported so far include:
@@ -51,10 +55,6 @@ Main features supported so far include:
* **Include graph diagram generation**
* Show include graph for selected files - [_example_](docs/test_cases/t40001.md)
To see what `clang-uml` can do, checkout the diagrams generated for unit
test cases [here](./docs/test_cases.md) and examples in
[clang-uml-examples](https://github.com/bkryza/clang-uml-examples) repository.
More comprehensive documentation can be found [here](./docs/README.md).
## Installation
@@ -210,7 +210,7 @@ public:
};
```
generates the following diagram (via PlantUML):
results in the following diagram (via PlantUML):
![class_diagram_example](docs/test_cases/t00014_class.svg)
@@ -295,7 +295,7 @@ int tmain()
}
```
generates the following diagram (via PlantUML):
results in the following diagram (via PlantUML):
![sequence_diagram_example](docs/test_cases/t20029_sequence.svg)
@@ -340,7 +340,7 @@ class B : public ns1::ns2::Anon {
}
```
generates the following diagram (via PlantUML):
results in the following diagram (via PlantUML):
![package_diagram_example](docs/test_cases/t30003_package.svg)
@@ -364,7 +364,7 @@ tests/t40001
```
generates the following diagram (via PlantUML) based on include directives in the code:
results in the following diagram (via PlantUML) based on include directives in the code:
![package_diagram_example](docs/test_cases/t40001_include.svg)
@@ -435,6 +435,7 @@ This project relies on the following great tools:
* [PlantUML](https://plantuml.com/) - language and diagram for generating UML diagrams
* [Catch2](https://github.com/catchorg/Catch2) - C++ unit test framework
* [glob](https://github.com/p-ranav/glob) - Unix style path expansion for C++
* [indicators](https://github.com/p-ranav/indicators) - Activity indicators for modern C++
* [CLI11](https://github.com/CLIUtils/CLI11) - command line parser for C++
* [inja](https://github.com/pantor/inja) - a template engine for modern C++
* [backward-cpp](https://github.com/bombela/backward-cpp) - stack trace pretty printer for C++

View File

@@ -40,6 +40,8 @@ To add an initial class diagram to your project, follow these steps:
3. Run `clang-uml` in the projects top directory:
```bash
$ clang-uml
# or to see generation progress for each diagram run
$ clang-uml --progress
```
4. Generate SVG images from the PlantUML diagrams:
```bash

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");
spdlog::register_logger(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 "
"found. Please make sure that your 'glob' patterns match "
"at least 1 file in 'compile_commands.json'.",
name);
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_{std::move(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,127 @@
/**
* 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{};
const auto kBarWidth = 35U;
const auto kPrefixTextWidth = 25U;
auto bar = std::make_shared<indicators::ProgressBar>(
indicators::option::BarWidth{kBarWidth},
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, kPrefixTextWidth))},
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{bar_index, 0, max});
progress_bars_mutex_.unlock();
}
void progress_indicator::increment(const std::string &name)
{
const auto kASTTraverseProgressPercent = 95U;
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 * kASTTraverseProgressPercent) / 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)
{
const auto kCompleteProgressPercent = 100U;
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(kCompleteProgressPercent);
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,62 @@
/**
* 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 {
explicit progress_state(size_t i, size_t p, size_t m)
: index{i}
, progress{p}
, max{m}
{
}
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: {}",

21
thirdparty/indicators/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Pranav
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

3236
thirdparty/indicators/indicators.hpp vendored Normal file

File diff suppressed because it is too large Load Diff