21#ifndef HEDGEHOG_DOT_PRINTER_H 
   22#define HEDGEHOG_DOT_PRINTER_H 
   36#include "../../core/abstractions/base/node/graph_node_abstraction.h" 
   37#include "../../core/abstractions/base/node/task_node_abstraction.h" 
   38#include "../../core/abstractions/base/any_groupable_abstraction.h" 
   45class DotPrinter : 
public Printer {
 
   54    std::string extraLabel_{}; 
 
   60    bool declarationPrinted_ = 
false; 
 
   62    core::abstraction::GraphNodeAbstraction *
 
   63        belongingGraph_ = 
nullptr; 
 
   70    explicit Edge(std::string 
id, std::string type, core::abstraction::GraphNodeAbstraction *
const belongingGraph)
 
   71        : id_(std::move(id)), type_(std::move(type)), belongingGraph_(belongingGraph) {}
 
   74    virtual ~Edge() = 
default;
 
   78    [[nodiscard]] std::string 
const &id()
 const { 
return id_; }
 
   82    [[nodiscard]] std::string 
const &extraLabel()
 const { 
return extraLabel_; }
 
   86    [[nodiscard]] core::abstraction::GraphNodeAbstraction *belongingGraph()
 const { 
return belongingGraph_; }
 
   90    void addExtraLabel(std::string extraLabel) { extraLabel_ = std::move(extraLabel); }
 
   94    void addArrival(std::string arrival) { arrivals_.insert(std::move(arrival)); }
 
   98    void addExit(std::string exit) { exits_.insert(std::move(exit)); }
 
  102    void printDeclaration(std::ostream &os) {
 
  103      if (!declarationPrinted_) {
 
  104        declarationPrinted_ = 
true;
 
  105        os << 
"\"" << id_ << 
"\"" << 
"[label=\"" << type_ << 
"\\n" << extraLabel_ << 
"\", shape=rect];\n";
 
  111    void printEdges(std::ostream &os)
 const {
 
  112      for (
auto &arrival : arrivals_) {
 
  113        os << 
"\"" << arrival << 
"\" -> \"" << id_ << 
"\"[penwidth=1, dir=none];\n";
 
  115      for (
auto &exit : exits_) {
 
  116        os << 
"\"" << id_ << 
"\" -> \"" << exit << 
"\"[penwidth=1];\n";
 
  123    bool operator==(Edge 
const &rhs)
 const { 
return id_ == rhs.id_ && type_ == rhs.type_; }
 
  126  std::ofstream outputFile_ = {}; 
 
  130  std::unique_ptr<ColorPicker> colorPicker_ = 
nullptr; 
 
  132  std::chrono::nanoseconds
 
  133      graphTotalExecution_ = std::chrono::nanoseconds::max(), 
 
  134  minExecutionDurationInAllGraphs_ =
 
  135  std::chrono::nanoseconds::max(), 
 
  136  maxExecutionDurationInAllGraphs_ =
 
  137  std::chrono::nanoseconds::min(), 
 
  138  rangeExecutionDurationInAllGraphs_ =
 
  139  std::chrono::nanoseconds::min(),  
 
  140  minWaitDurationInAllGraphs_ =
 
  141  std::chrono::nanoseconds::max(),  
 
  142  maxWaitDurationInAllGraphs_ =
 
  143  std::chrono::nanoseconds::min(),  
 
  144  rangeWaitDurationInAllGraphs_ =
 
  145  std::chrono::nanoseconds::min();  
 
  147  std::unique_ptr<std::vector<Edge>> edges_ = 
nullptr; 
 
  160  DotPrinter(std::filesystem::path 
const &dotFilePath,
 
  161             ColorScheme colorScheme,
 
  162             StructureOptions structureOptions,
 
  163             DebugOptions debugOptions,
 
  164             core::abstraction::GraphNodeAbstraction 
const *graph,
 
  165             std::unique_ptr<ColorPicker> colorPicker,
 
  167      : colorScheme_(colorScheme),
 
  168        structureOptions_(structureOptions),
 
  169        debugOptions_(debugOptions),
 
  170        colorPicker_(std::move(colorPicker)),
 
  171        edges_(std::make_unique<std::vector<Edge>>()) {
 
  172    assert(graph != 
nullptr);
 
  174    testPath(dotFilePath, verbose);
 
  176    if (colorPicker_ == 
nullptr) {
 
  178          std::runtime_error(
"A dot printer should be constructed with a valid ColorPicker (colorPicker != nullptr)")
 
  182    auto minMaxExecTime = graph->minMaxExecutionDuration();
 
  183    auto minMaxWaitTime = graph->minMaxWaitDuration();
 
  185    minExecutionDurationInAllGraphs_ = minMaxExecTime.first;
 
  186    maxExecutionDurationInAllGraphs_ = minMaxExecTime.second;
 
  188    minWaitDurationInAllGraphs_ = minMaxWaitTime.first;
 
  189    maxWaitDurationInAllGraphs_ = minMaxWaitTime.second;
 
  192    rangeExecutionDurationInAllGraphs_ =
 
  193        maxExecutionDurationInAllGraphs_ == minExecutionDurationInAllGraphs_ ?
 
  194        std::chrono::nanoseconds(1) :
 
  195        maxExecutionDurationInAllGraphs_ - minExecutionDurationInAllGraphs_;
 
  197    rangeWaitDurationInAllGraphs_ =
 
  198        maxWaitDurationInAllGraphs_ == minWaitDurationInAllGraphs_ ?
 
  199        std::chrono::nanoseconds(1) :
 
  200        maxWaitDurationInAllGraphs_ - minWaitDurationInAllGraphs_;
 
  202    graphTotalExecution_ =
 
  203        graph->executionDuration() == std::chrono::nanoseconds::zero() ?
 
  204        std::chrono::system_clock::now() - graph->startExecutionTimeStamp() : graph->executionDuration();
 
  208  ~DotPrinter()
 override { outputFile_.close(); }
 
  212  void printGraphHeader(core::abstraction::GraphNodeAbstraction 
const *graph)
 override {
 
  214    if (!graph->isRegistered()) {
 
  216          << 
"digraph " << graph->id()
 
  217          << 
" {\nlabel=\"" << graph->name();
 
  218      if (debugOptions_ == DebugOptions::ALL) { outputFile_ << 
" " << graph->id(); }
 
  220      outputFile_ << 
"\\nExecution duration:" << durationPrinter(this->graphTotalExecution_)
 
  221                  << 
"\\nCreation duration:" << durationPrinter(graph->graphConstructionDuration())
 
  222                  << 
"\"; fontsize=25; penwidth=5; labelloc=top; labeljust=left; \n";
 
  226      outputFile_ << 
"subgraph cluster" << graph->id() << 
" {\nlabel=\"" << graph->name();
 
  227      if (debugOptions_ == DebugOptions::ALL) {
 
  228        outputFile_ << 
" " << graph->id();
 
  231          << 
"\"; fontsize=25; penwidth=5; fillcolor=\"" 
  232          << colorFormatConvertor(graph->printOptions().background())
 
  240  void printGraphFooter(core::abstraction::GraphNodeAbstraction 
const *graph)
 override {
 
  243    for (
auto &edge : *edges_) {
 
  244      if (edge.belongingGraph() == graph) {
 
  245        edge.printDeclaration(outputFile_);
 
  250    if (!graph->isRegistered()) {
 
  253      for (
auto const &edge : *(this->edges_)) { edge.printEdges(outputFile_); }
 
  256    outputFile_ << 
"}\n";
 
  262  void printNodeInformation(core::abstraction::NodeAbstraction 
const *node)
 override {
 
  264    if (
auto task = 
dynamic_cast<core::abstraction::TaskNodeAbstraction 
const *
>(node)) {
 
  266      if (this->structureOptions_ == StructureOptions::ALL || this->structureOptions_ == StructureOptions::THREADING) {
 
  268        outputFile_ << getTaskInformation(task);
 
  271        if (
auto copyableNode = 
dynamic_cast<core::abstraction::AnyGroupableAbstraction 
const *
>(task)) {
 
  273          if (copyableNode == copyableNode->groupRepresentative()) {
 
  275            outputFile_ << getTaskInformation(task);
 
  279          outputFile_ << getTaskInformation(task);
 
  292  void printEdge(core::abstraction::NodeAbstraction 
const *from, core::abstraction::NodeAbstraction 
const *to,
 
  293                 std::string 
const &edgeType,
 
  294                 size_t const &queueSize, 
size_t const &maxQueueSize)
 override {
 
  296    std::ostringstream oss;
 
  297    std::string idToFind, label;
 
  299    for (
auto &source : from->ids()) {
 
  300      for (
auto &dest : to->ids()) {
 
  301        auto edge = getOrCreateEdge(dest.second, edgeType, to->belongingGraph());
 
  303        if (edge->extraLabel().empty()) {
 
  304          if (this->structureOptions_ == StructureOptions::QUEUE || this->structureOptions_ == StructureOptions::ALL) {
 
  305            oss << 
"QS=" << queueSize << 
"\\nMQS=" << maxQueueSize;
 
  306            edge->addExtraLabel(oss.str());
 
  310        edge->addArrival(source.first);
 
  311        edge->addExit(dest.first);
 
  313        if (this->structureOptions_ == StructureOptions::THREADING ||
 
  314            this->structureOptions_ == StructureOptions::ALL) {
 
  315          if (
auto copyableSender = 
dynamic_cast<core::abstraction::AnyGroupableAbstraction 
const *
>(from)) {
 
  316            for (
auto groupMember : *copyableSender->group()) { edge->addArrival(groupMember->nodeId()); }
 
  327  void printGroup(core::abstraction::NodeAbstraction *representative,
 
  328                  std::vector<core::abstraction::NodeAbstraction *> 
const &group)
 override {
 
  329    bool const printAllGroupMembers =
 
  330        this->structureOptions_ == StructureOptions::THREADING || this->structureOptions_ == StructureOptions::ALL;
 
  332    auto copyableRepr = 
dynamic_cast<core::abstraction::AnyGroupableAbstraction *
>(representative);
 
  333    auto printRepr = 
dynamic_cast<core::abstraction::PrintableAbstraction *
>(representative);
 
  334    if (copyableRepr == 
nullptr || printRepr == 
nullptr) {
 
  335      std::ostringstream oss;
 
  336      oss << 
"Internal error in: " << __FUNCTION__
 
  337          << 
" a group of node should be created with node that derives from AnyGroupableAbstraction and PrintableAbstraction";
 
  338      throw std::runtime_error(oss.str());
 
  343    if (printAllGroupMembers) {
 
  345      outputFile_ << 
"subgraph cluster" << representative->id()
 
  346                  << 
" {\nlabel=\"\"; penwidth=3; style=filled; fillcolor=\"#ebf0fa\"; color=\"#4e78cf\";\n";
 
  350    printRepr->visit(
this);
 
  352    if (printAllGroupMembers) {
 
  353      for (
auto groupMember : group) {
 
  354        if(
auto printGroupMember = 
dynamic_cast<core::abstraction::PrintableAbstraction *
>(groupMember)){
 
  355          printGroupMember->visit(
this);
 
  357          std::ostringstream oss;
 
  358          oss << 
"Internal error in: " << __FUNCTION__
 
  359              << 
" a group of node should be created with nodes that derive from AnyGroupableAbstraction and PrintableAbstraction";
 
  360          throw std::runtime_error(oss.str());
 
  365    if (printAllGroupMembers) {
 
  366      outputFile_ << 
"}\n";
 
  374  void printSource(core::abstraction::NodeAbstraction 
const *source)
 override {
 
  375    outputFile_ << source->id() << 
" [label=\"" << source->name();
 
  376    if (debugOptions_ == DebugOptions::ALL) {
 
  377      outputFile_ << 
" " << source->id() << 
" \\(Graph:" << source->belongingGraph()->id() << 
"\\)";
 
  379    outputFile_ << 
"\", shape=invhouse];\n";
 
  385  void printSink(core::abstraction::NodeAbstraction 
const *sink)
 override {
 
  386    outputFile_ << sink->id() << 
" [label=\"" << sink->name();
 
  387    if (debugOptions_ == DebugOptions::ALL) {
 
  388      outputFile_ << 
" " << sink->id() << 
" \\(Graph:" << sink->belongingGraph()->id() << 
"\\)";
 
  390    outputFile_ << 
"\", shape=point];\n";
 
  397  void printExecutionPipelineHeader(core::abstraction::ExecutionPipelineNodeAbstraction 
const *ep,
 
  398                                    core::abstraction::NodeAbstraction 
const *switchNode)
 override {
 
  400    outputFile_ << 
"subgraph cluster" << ep->id() << 
" {\nlabel=\"" << ep->name();
 
  401    if (debugOptions_ == DebugOptions::ALL) { outputFile_ << 
" " << ep->id() << 
" / " << switchNode->id(); }
 
  403    outputFile_ << 
"\"; penwidth=1; style=dotted; style=filled; fillcolor=\"" 
  404                << colorFormatConvertor(ep->printOptions().background())
 
  406                << switchNode->id() << 
"[label=\"\", shape=triangle];\n";
 
  411  void printExecutionPipelineFooter()
 override {
 
  412    outputFile_ << 
"}\n";
 
  422  std::vector<Edge>::iterator getOrCreateEdge(
 
  423      std::string 
const &
id, std::string 
const &type,
 
  425    std::ostringstream ossId;
 
  426    ossId << 
"edge" << 
id << type;
 
  427    Edge temp(ossId.str(), type, belongingGraph);
 
  428    auto it = std::find(this->edges_->begin(), this->edges_->end(), temp);
 
  429    if (it != this->edges_->end()) { 
return it; }
 
  430    else { 
return this->edges_->insert(this->edges_->end(), temp); }
 
  436  std::string getTaskInformation(core::abstraction::TaskNodeAbstraction 
const *task) {
 
  437    std::stringstream ss;
 
  439    auto copyableTask = 
dynamic_cast<core::abstraction::AnyGroupableAbstraction 
const *
>(task);
 
  440    auto slotTask = 
dynamic_cast<core::abstraction::SlotAbstraction 
const *
>(task);
 
  443        this->structureOptions_ == StructureOptions::THREADING || this->structureOptions_ == StructureOptions::ALL;
 
  446    ss << task->id() << 
" [label=\"" << task->name();
 
  448    if (debugOptions_ == DebugOptions::ALL) {
 
  449      ss << 
" " << task->id() << 
" \\(" << task->belongingGraph()->id() << 
"\\)";
 
  453    if (!printAllNodes) {
 
  454      if (copyableTask && copyableTask->isInGroup()) {
 
  455        ss << 
" x " << copyableTask->numberThreads();
 
  459    if (debugOptions_ == DebugOptions::ALL) {
 
  462        ss << 
"\\nActive inputs connection: " << slotTask->connectedNotifiers().size();
 
  466        ss << 
"\\nThread Active?: " << std::boolalpha << task->isActive();
 
  470          ss << 
"\\nActive threads: " << copyableTask->numberActiveThreadInGroup();
 
  475    if (printAllNodes || !copyableTask) {
 
  476      ss << 
"\\nNumber Elements Received: " << task->numberReceivedElements();
 
  477      ss << 
"\\nWait Duration: " << durationPrinter(task->waitDuration());
 
  478      ss << 
"\\nDequeue + Execution Duration: " << durationPrinter(task->executionDuration());
 
  479      ss << 
"\\nExecution Duration Per Element: " << durationPrinter(task->perElementExecutionDuration());
 
  480      if (task->hasMemoryManagerAttached()) {
 
  481        ss << 
"\\nMemory manager (" << task->memoryManager()->managedType() << 
"): " 
  482           << task->memoryManager()->currentSize() << 
"/" << task->memoryManager()->capacity();
 
  483        ss << 
"\\nMemory Wait Duration: " << durationPrinter(task->memoryWaitDuration());
 
  490        ss << 
"\\nNumber of Elements Received Per Task: ";
 
  491        if (copyableTask->numberThreads() > 1) {
 
  492          auto minmaxElements = copyableTask->minmaxNumberElementsReceivedGroup();
 
  493          auto meanSDNumberElements = copyableTask->meanSDNumberElementsReceivedGroup();
 
  495             << 
"  Min: " << minmaxElements.first << 
"\\n" 
  496             << 
"  Avg: " << std::setw(3) << meanSDNumberElements.first
 
  497             << 
" +- " << std::setw(3) << meanSDNumberElements.second
 
  499             << 
"  Max: " << std::setw(3) << minmaxElements.second << 
"\\n";
 
  501          ss << task->numberReceivedElements() << 
"\\n";
 
  505        if (copyableTask->numberThreads() > 1) {
 
  506          auto minmaxWait = copyableTask->minmaxWaitDurationGroup();
 
  507          auto meanSDWait = copyableTask->meanSDWaitDurationGroup();
 
  509             << 
"  Min: " << durationPrinter(minmaxWait.first) << 
"\\n" 
  510             << 
"  Avg: " << durationPrinter(meanSDWait.first) << 
" +- " 
  511             << durationPrinter(meanSDWait.second) << 
"\\n" 
  512             << 
"  Max: " << durationPrinter(minmaxWait.second) << 
"\\n";
 
  514          ss << durationPrinter(task->waitDuration()) << 
"\\n";
 
  517        ss << 
"Dequeue + Execution Time: ";
 
  518        if (copyableTask->numberThreads() > 1) {
 
  519          auto minmaxExec = copyableTask->minmaxExecutionDurationGroup();
 
  520          auto meanSDExec = copyableTask->meanSDExecutionDurationGroup();
 
  522             << 
"  Min: " << durationPrinter(minmaxExec.first) << 
"\\n" 
  523             << 
"  Avg: " << durationPrinter(meanSDExec.first) << 
" +- " 
  524             << durationPrinter(meanSDExec.second) << 
"\\n" 
  525             << 
"  Max: " << durationPrinter(minmaxExec.second) << 
"\\n";
 
  527          ss << durationPrinter(task->executionDuration()) << 
"\\n";
 
  531        ss << 
"Execution Time Per Element: ";
 
  532        if (copyableTask->numberThreads() > 1) {
 
  533          auto minmaxExecPerElement = copyableTask->minmaxExecTimePerElementGroup();
 
  534          auto meanSDExecPerElement = copyableTask->meanSDExecTimePerElementGroup();
 
  536             << 
"  Min: " << durationPrinter(minmaxExecPerElement.first) << 
"\\n" 
  537             << 
"  Avg: " << durationPrinter(meanSDExecPerElement.first) << 
" +- " 
  538             << durationPrinter(meanSDExecPerElement.second) << 
"\\n" 
  539             << 
"  Max: " << durationPrinter(minmaxExecPerElement.second) << 
"\\n";
 
  541          ss << durationPrinter(task->perElementExecutionDuration()) << 
"\\n";
 
  545        if (task->hasMemoryManagerAttached()) {
 
  546          ss << 
"Memory manager (" << task->memoryManager()->managedType() << 
"): " 
  547             << task->memoryManager()->currentSize() << 
"/" << task->memoryManager()->capacity();
 
  548          ss << 
"\\nMemory Wait Time: ";
 
  549          if (copyableTask->numberThreads() == 1) {
 
  550            ss << durationPrinter(task->memoryWaitDuration()) << 
"\\n";
 
  552            auto minmaxWaitMemory = copyableTask->minmaxMemoryWaitTimeGroup();
 
  553            auto meanSDWaitMemory = copyableTask->meanSDMemoryWaitTimePerElementGroup();
 
  555               << 
"  Min: " << durationPrinter(minmaxWaitMemory.first) << 
"\\n" 
  556               << 
"  Avg: " << durationPrinter(meanSDWaitMemory.first) << 
" +-" 
  557               << durationPrinter(meanSDWaitMemory.second) << 
"\\n" 
  558               << 
"  Max: " << durationPrinter(minmaxWaitMemory.second) << 
"\\n";
 
  564    auto extraInfo = task->extraPrintingInformation();
 
  565    if (!extraInfo.empty()) { ss << 
"\\n" << extraInfo; }
 
  570    switch (this->colorScheme_) {
 
  571      case ColorScheme::EXECUTION:ss << 
",color=" << this->getExecRGB(task->executionDuration()) << 
", penwidth=3";
 
  573      case ColorScheme::WAIT:ss << 
",color=" << this->getWaitRGB(task->waitDuration()) << 
", penwidth=3";
 
  578    ss << R
"(, style=filled, fillcolor=")" 
  579       << colorFormatConvertor(task->printOptions().background()) 
  580       << R"(", fontcolor=")" 
  581       << colorFormatConvertor(task->printOptions().font()) 
  590  void testPath(std::filesystem::path 
const &dotFilePath, 
bool verbose) {
 
  591    auto directoryPath = dotFilePath.parent_path();
 
  592    std::ostringstream oss;
 
  593    if (!dotFilePath.has_filename()) {
 
  594      oss << 
"The path: " << dotFilePath << 
" does not represent a file.";
 
  595      throw (std::runtime_error(oss.str()));
 
  597    if (!std::filesystem::exists(directoryPath)) {
 
  598      oss << 
"The file " << dotFilePath.filename() << 
" can not be store in " << directoryPath
 
  599          << 
" because the directory does not  exist.";
 
  600      throw (std::runtime_error(oss.str()));
 
  602    if (!std::filesystem::exists(directoryPath)) {
 
  603      oss << 
"The file " << dotFilePath.filename() << 
" can not be store in " << directoryPath
 
  604          << 
" because the directory does not  exist.";
 
  605      throw (std::runtime_error(oss.str()));
 
  608      if (std::filesystem::exists(dotFilePath)) {
 
  609        std::cout << 
"The file " << dotFilePath.filename() << 
" will be overwritten." << std::endl;
 
  611        std::cout << 
"The file " << dotFilePath.filename() << 
" will be created." << std::endl;
 
  614    outputFile_ = std::ofstream(dotFilePath);
 
  620  std::string getExecRGB(std::chrono::nanoseconds 
const &ns) {
 
  622        ->getRGBFromRange(ns, this->minExecutionDurationInAllGraphs_, this->rangeExecutionDurationInAllGraphs_);
 
  628  std::string getWaitRGB(std::chrono::nanoseconds 
const &ns) {
 
  629    return colorPicker_->getRGBFromRange(ns, this->minWaitDurationInAllGraphs_, this->rangeWaitDurationInAllGraphs_);
 
  635  static std::string durationPrinter(std::chrono::nanoseconds 
const &ns) {
 
  636    std::ostringstream oss;
 
  639    auto s = std::chrono::duration_cast<std::chrono::seconds>(ns);
 
  640    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(ns);
 
  641    auto us = std::chrono::duration_cast<std::chrono::microseconds>(ns);
 
  643    if (s > std::chrono::seconds::zero()) {
 
  644      oss << s.count() << 
"." << std::setfill(
'0') << std::setw(3) << (ms - s).count() << 
"s";
 
  645    } 
else if (ms > std::chrono::milliseconds::zero()) {
 
  646      oss << ms.count() << 
"." << std::setfill(
'0') << std::setw(3) << (us - ms).count() << 
"ms";
 
  647    } 
else if (us > std::chrono::microseconds::zero()) {
 
  648      oss << us.count() << 
"." << std::setfill(
'0') << std::setw(3) << (ns - us).count() << 
"us";
 
  650      oss << std::setw(3) << ns.count() << 
"ns";
 
  659    std::ostringstream ss;
 
  661       << std::setw(2) << std::setfill(
'0') << std::hex << (
int) color.
r_ 
  662       << std::setw(2) << std::setfill(
'0') << std::hex << (
int) color.
g_ 
  663       << std::setw(2) << std::setfill(
'0') << std::hex << (
int) color.
b_ 
  664       << std::setw(2) << std::setfill(
'0') << std::hex << (
int) color.
a_;
 
StructureOptions
Enum structural options.
DebugOptions
Enum to enable debug printing.
ColorScheme
Enum color options.
Base graph node abstraction.
Simple color representation.
uint8_t g_
Green color value.
uint8_t a_
Alpha color value.
uint8_t b_
Blue color value.
uint8_t r_
Red color value.