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.