diff --git a/src/lib/dot-gen.hpp b/src/lib/dot-gen.hpp index b04784bce..e3f940d34 100644 --- a/src/lib/dot-gen.hpp +++ b/src/lib/dot-gen.hpp @@ -22,9 +22,41 @@ /** @file dot-gen.hpp ** Support for generation of Graphviz-DOT code for structure visualisation. + ** The [dot language] offers a simple notation to represent structural information + ** as diagrams of abstract graphs and networks. The *Graphviz* layout programs + ** translate these into automatically arranged diagrams graphs, relying on basic + ** layout schemes like directed and undirected graphs, force-directed placement, + ** radial arrangements, clustered graphs or squarified treemap layout. These + ** visualisations can be rendered as images, vector graphic SVG or PDF and + ** a lot of further formats. + ** + ** The namespace lib::dot_gen contains a set of integrated builder DSL functions + ** to simplify the task of syntax generation; notable it is possible to set up + ** several [sections](\ref lib::dot_gen::Section), which can then be gradually + ** populated with definition clauses while traversing a data structure. + ** + ** @todo 11/2023 this is an initial draft, shaped by the immediate need to visualise + ** [random generated](\ref vault::gear::test::TestChainLoad) computation + ** patterns for Scheduler [load testing](\ref SchedulerStress_test). + ** The abstraction level of this DSL is low and structures closely match + ** some clauses of the DOT language; this approach may not yet be adequate + ** to generate more complex graph structures and was extracted as a starting + ** point for further refinements. + ** + ** ## Usage + ** The top-level entrance point is lib::dot_get::digraph(), allowing to combine a + ** series of lib::dot_gen::Section definitions into a DOT script, which can then + ** be retrieved by string conversion (or sent to standard output) + ** - Section is an accumulator of lines with DOT language specs + ** - Code is a string with syntax, used as base for some pre-configured terms + ** - Node defines a variable name, but can be augmented with attributes to + ** build a _node-statement_ + ** - Scope is meant as a device to group several nodes together, typically to + ** form a cluster or stratum in the generated layout ** ** @see TestChainLoad_test ** @see SchedulerStress_test + ** [dot language]: https://graphviz.org/doc/info/lang.html */ @@ -32,407 +64,182 @@ #define LIB_DOT_GEN_H -#include "vault/common.hpp" -#include "lib/test/test-helper.hpp" +#include "lib/format-util.hpp" +#include "lib/util.hpp" -//#include "vault/gear/job.h" -//#include "vault/gear/activity.hpp" -//#include "vault/gear/nop-job-functor.hpp" -//#include "lib/time/timevalue.hpp" -//#include "lib/meta/variadic-helper.hpp" -//#include "lib/meta/function.hpp" -//#include "lib/wrapper.hpp" -#include "lib/format-util.hpp" /////////////////TODO used only for dot generation -#include "lib/util.hpp" /////////////////TODO used only for dot generation - -#include -#include #include -//#include -//#include -#include -#include /////////////////TODO used only for dot generation +#include #include -#include /////////////////TODO used only for dot generation -#include +#include namespace lib { +namespace dot_gen { ///< Collection of builder DSL functions to generate Graphviz-DOT code -// using std::string; -// using std::function; -// using lib::time::TimeValue; -// using lib::time::Time; -// using lib::time::FSecs; -// using lib::time::Offset; -// using lib::meta::RebindVariadic; - using util::toString; /////////////////TODO used only for dot generation - using util::isnil; /////////////////TODO used only for dot generation - using util::max; - using util::unConst; -// using std::forward; + using util::toString; + using util::isnil; using std::string; - using std::swap; using std::move; - using boost::hash_combine; - namespace {// Diagnostic markers -// const string MARK_INC{"IncSeq"}; -// const string MARK_SEQ{"Seq"}; - const size_t DEFAULT_FAN = 16; - const size_t DEFAULT_SIZ = 256; - -// using SIG_JobDiagnostic = void(Time, int32_t); - } - - - namespace dot { - - struct Code : string - { - using string::string; - Code(string const& c) : string{c} { } - Code(string && c) : string{move(c)}{ } - }; - - struct Section - { - std::vector lines; - - Section (string name) - : lines{"// "+name} - { } - - Section&& - operator+= (Code const& code) - { - lines.emplace_back(code); - return move(*this); - } - }; - - /** - * Helper to generate DOT-Graphviz rendering of topology - */ - class DotOut - { - std::ostringstream buff_; - - static uint const IDENT_STEP = 2; - public: - void - putLine (string line, uint indent=0) - { - if (indent) - buff_ << string(indent,' '); - buff_ << line - << '\n'; - } - - void - put (Code const& code) - { - buff_ << code; - } - - void - put (Section const& sect) - { - for (string const& line : sect.lines) - putLine (line, IDENT_STEP); - } - - template - void - put (P const& part, PS const& ...parts) - { - put (part); - putLine (""); - put (parts...); - } - - /** retrieve complete code generated thus far */ - operator string() const - { - return buff_.str(); - } - }; - - struct Node : Code - { - Node (size_t id) - : Code{"N"+toString(id)} - { } - - Node&& - addAttrib (string def) - { - if (back() != ']') - append ("["); - else - { - resize (length()-2); - append (", "); - } - append (def+" ]"); - return move(*this); - } - - Node&& - label (size_t i) - { - return addAttrib ("label="+toString(i)); - } - - Node&& - style (Code const& code) - { - if (not isnil(code)) - addAttrib (code); - return move(*this); - } - }; - - struct Scope : Code - { - Scope (size_t id) - : Code{"{ /*"+toString(id)+"*/ }"} - { } - - Scope&& - add (Code const& code) - { - resize(length()-1); - append (code+" }"); - return move(*this); - } - - Scope&& - rank (string rankSetting) - { - return add(Code{"rank="+rankSetting}); - } - }; - - inline Code - connect (size_t src, size_t dest) + /** markup to generate a piece of code */ + struct Code : string { - return Code{Node(src) +" -> "+ Node(dest)}; - } - - template - inline DotOut - digraph (COD ...parts) - { - DotOut script; - script.putLine (Code{"digraph {"}); - script.put (parts...); - script.putLine (Code{"}"}); - return script; - } - - - } - - - - /** - * A Generator for synthetic Render Jobs for Scheduler load testing. - * @tparam maxFan maximal fan-in/out from a node, also limits maximal parallel strands. - * @see TestChainLoad_test - */ - template - class TestChainLoad - : util::MoveOnly - { - - public: - struct Node - : util::MoveOnly - { - using _Arr = std::array; - using Iter = typename _Arr::iterator; - - /** Table with connections to other Node records */ - struct Tab : _Arr - { - Iter after = _Arr::begin(); - Iter end() { return after; } - friend Iter end(Tab& tab) { return tab.end(); } - - void clear() { after = _Arr::begin(); } ///< @warning pointer data in array not cleared - - size_t size() const { return unConst(this)->end()-_Arr::begin(); } - bool empty() const { return 0 == size(); } - - Iter - add(Node* n) - { - if (after != _Arr::end()) - { - *after = n; - return after++; - } - NOTREACHED ("excess node linkage"); - } - - }; - - size_t hash; - size_t level{0}, repeat{0}; - Tab pred{0}, succ{0}; - - Node(size_t seed =0) - : hash{seed} - { } - - void - clear() - { - hash = 0; - level = repeat = 0; - pred.clear(); - succ.clear(); - } - - Node& - addPred (Node* other) - { - REQUIRE (other); - pred.add (other); - other->succ.add (this); - return *this; - } - - Node& - addSucc (Node* other) - { - REQUIRE (other); - succ.add (other); - other->pred.add (this); - return *this; - } - Node& addPred(Node& other) { return addPred(&other); } - Node& addSucc(Node& other) { return addSucc(&other); } - - size_t - calculate() - { - for (Node*& entry: pred) - if (entry) - hash_combine (hash, entry->hash); - return hash; - } - }; - - private: - using NodeTab = typename Node::Tab; - using NodeStorage = std::array; - using CtrlRule = std::function; - - std::unique_ptr nodes_; - - CtrlRule seedingRule_ {[](size_t, double){ return 0; }}; - CtrlRule expansionRule_{[](size_t, double){ return 0; }}; - CtrlRule reductionRule_{[](size_t, double){ return 0; }}; - - public: - TestChainLoad() - : nodes_{new NodeStorage} - { } - - - size_t size() const { return nodes_->size(); } - size_t topLevel() const { return nodes_->back().level; } - size_t getSeed() const { return nodes_->front().hash; } - size_t getHash() const { return nodes_->back().hash; } - - - /* ===== topology control ===== */ - - /** - * Use current configuration and seed to (re)build Node connectivity. - */ - TestChainLoad - buildToplolgy() - { - NodeTab a,b, // working data for generation - *curr{&a}, // the current set of nodes to carry on - *next{&b}; // the next set of nodes connected to current - Node* node = &nodes_->front(); - size_t level{0}; - size_t expectedLevel = max (1u, numNodes/maxFan); - - // prepare building blocks for the topology generation... - auto height = [&](double level) - { - return level/expectedLevel; - }; - auto spaceLeft = [&]{ return next->size() < maxFan; }; - auto addNode = [&]{ - Node* n = *next->add (node++); - n->clear(); - n->level = level; - return n; - }; - auto apply = [&](CtrlRule& rule, Node* n) - { - return rule (n->hash, height(level)); - }; - - addNode(); // prime next with root node - // visit all further nodes and establish links - while (node < &nodes_->back()) - { - ++level; - curr->clear(); - swap (next, curr); - size_t toReduce{0}; - Node* r; - REQUIRE (spaceLeft()); - for (Node* o : *curr) - { // follow-up on all Nodes in current level... - o->calculate(); - size_t toSeed = apply (seedingRule_, o); - size_t toExpand = apply (expansionRule_,o); - while (0 < toSeed and spaceLeft()) - { // start a new chain from seed - Node* n = addNode(); - n->hash = this->getSeed(); - --toSeed; - } - while (0 < toExpand and spaceLeft()) - { // fork out secondary chain from o - Node* n = addNode(); - o->addSucc(n); - --toExpand; - } - if (not toReduce and spaceLeft()) - { // carry-on the chain from o - r = addNode(); - toReduce = apply (reductionRule_, o); - } - else - --toReduce; - ENSURE (r); - r->addPred(o); - } - } - ENSURE (node == &nodes_->back()); - // connect ends of all remaining chains to top-Node - node->clear(); - node->level = ++level; - for (Node* o : *next) - node->addPred(o); - // - return move(*this); - } - - private: + using string::string; + Code(string const& c) : string{c} { } + Code(string && c) : string{move(c)}{ } }; + /** Accumulator to collect lines of DOT code */ + struct Section + { + std::vector lines; + + Section (string name) + : lines{"// "+name} + { } + + Section&& + operator+= (Code const& code) + { + lines.emplace_back(code); + return move(*this); + } + }; -} // namespace lib + + /** Helper to collect DOT-Graphviz code for output */ + class DotOut + { + std::ostringstream buff_; + + static uint const IDENT_STEP = 2; + public: + void + putLine (string line, uint indent=0) + { + if (indent) + buff_ << string(indent,' '); + buff_ << line + << '\n'; + } + + void + put (Code const& code) + { + buff_ << code; + } + + void + put (Section const& sect) + { + for (string const& line : sect.lines) + putLine (line, IDENT_STEP); + } + + template + void + put (P const& part, PS const& ...parts) + { + put (part); + putLine (""); + put (parts...); + } + + /** retrieve complete code generated thus far */ + operator string() const + { + return buff_.str(); + } + }; + + + /** generate a Node name or a node_statement + * defining attributes of that node. All variables + * use the format `N`. */ + struct Node : Code + { + Node (size_t id) + : Code{"N"+toString(id)} + { } + + Node&& + addAttrib (string def) + { + if (back() != ']') + append ("["); + else + { + resize (length()-2); + append (", "); + } + append (def+" ]"); + return move(*this); + } + + Node&& + label (size_t i) + { + return addAttrib ("label="+toString(i)); + } + + Node&& + style (Code const& code) + { + if (not isnil(code)) + addAttrib (code); + return move(*this); + } + }; + + + /** accumulator to collect nodes grouped into a scope */ + struct Scope : Code + { + Scope (size_t id) + : Code{"{ /*"+toString(id)+"*/ }"} + { } + + Scope&& + add (Code const& code) + { + resize(length()-1); + append (code+" }"); + return move(*this); + } + + Scope&& + rank (string rankSetting) + { + return add(Code{"rank="+rankSetting}); + } + }; + + /** generate a directed node connectivity clause */ + inline Code + connect (size_t src, size_t dest) + { + return Code{Node(src) +" -> "+ Node(dest)}; + } + + + /** + * Entrance-point: generate a graph spec in DOT-Language. + * @param parts a sequence of Section or Code objects to be combined and rendered + * @return DotOut object holding the script rendered into a stringstream-buffer + */ + template + inline DotOut + digraph (COD ...parts) + { + DotOut script; + script.putLine (Code{"digraph {"}); + script.put (parts...); + script.putLine (Code{"}"}); + return script; + } + + +}} // namespace lib::dot_gen #endif /*LIB_DOT_GEN_H*/ diff --git a/tests/vault/gear/test-chain-load.hpp b/tests/vault/gear/test-chain-load.hpp index d96bdd184..cca15b3c1 100644 --- a/tests/vault/gear/test-chain-load.hpp +++ b/tests/vault/gear/test-chain-load.hpp @@ -61,7 +61,10 @@ ** [scheduled as Render Jobs](\ref TestChainLoad::scheduleJobs). ** ** ## Observation tools - ** - jaleck + ** The generated topology can be visualised as a graph, using the Graphviz-DOT language. + ** Nodes are rendered from bottom to top, organised into strata according to the time-level + ** and showing predecessor -> successor connectivity. Seed nodes are distinguished by + ** circular shape. ** ** @see TestChainLoad_test ** @see SchedulerStress_test @@ -83,8 +86,7 @@ //#include "lib/meta/function.hpp" //#include "lib/wrapper.hpp" #include "lib/iter-explorer.hpp" -#include "lib/format-util.hpp" /////////////////TODO used only for dot generation -#include "lib/util.hpp" /////////////////TODO used only for dot generation +#include "lib/dot-gen.hpp" #include #include @@ -92,9 +94,7 @@ //#include //#include #include -#include /////////////////TODO used only for dot generation #include -#include /////////////////TODO used only for dot generation #include @@ -109,173 +109,19 @@ namespace test { // using lib::time::FSecs; // using lib::time::Offset; // using lib::meta::RebindVariadic; - using util::toString; /////////////////TODO used only for dot generation - using util::isnil; /////////////////TODO used only for dot generation using util::max; using util::unConst; // using std::forward; - using std::string; +// using std::string; using std::swap; using std::move; using boost::hash_combine; + namespace dot = lib::dot_gen; - namespace {// Diagnostic markers -// const string MARK_INC{"IncSeq"}; -// const string MARK_SEQ{"Seq"}; + namespace { // Default definitions for topology generation const size_t DEFAULT_FAN = 16; const size_t DEFAULT_SIZ = 256; - -// using SIG_JobDiagnostic = void(Time, int32_t); - } - - - namespace dot { - - struct Code : string - { - using string::string; - Code(string const& c) : string{c} { } - Code(string && c) : string{move(c)}{ } - }; - - struct Section - { - std::vector lines; - - Section (string name) - : lines{"// "+name} - { } - - Section&& - operator+= (Code const& code) - { - lines.emplace_back(code); - return move(*this); - } - }; - - /** - * Helper to generate DOT-Graphviz rendering of topology - */ - class DotOut - { - std::ostringstream buff_; - - static uint const IDENT_STEP = 2; - public: - void - putLine (string line, uint indent=0) - { - if (indent) - buff_ << string(indent,' '); - buff_ << line - << '\n'; - } - - void - put (Code const& code) - { - buff_ << code; - } - - void - put (Section const& sect) - { - for (string const& line : sect.lines) - putLine (line, IDENT_STEP); - } - - template - void - put (P const& part, PS const& ...parts) - { - put (part); - putLine (""); - put (parts...); - } - - /** retrieve complete code generated thus far */ - operator string() const - { - return buff_.str(); - } - }; - - struct Node : Code - { - Node (size_t id) - : Code{"N"+toString(id)} - { } - - Node&& - addAttrib (string def) - { - if (back() != ']') - append ("["); - else - { - resize (length()-2); - append (", "); - } - append (def+" ]"); - return move(*this); - } - - Node&& - label (size_t i) - { - return addAttrib ("label="+toString(i)); - } - - Node&& - style (Code const& code) - { - if (not isnil(code)) - addAttrib (code); - return move(*this); - } - }; - - struct Scope : Code - { - Scope (size_t id) - : Code{"{ /*"+toString(id)+"*/ }"} - { } - - Scope&& - add (Code const& code) - { - resize(length()-1); - append (code+" }"); - return move(*this); - } - - Scope&& - rank (string rankSetting) - { - return add(Code{"rank="+rankSetting}); - } - }; - - inline Code - connect (size_t src, size_t dest) - { - return Code{Node(src) +" -> "+ Node(dest)}; - } - - template - inline DotOut - digraph (COD ...parts) - { - DotOut script; - script.putLine (Code{"digraph {"}); - script.put (parts...); - script.putLine (Code{"}"}); - return script; - } - - } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 8d3d4845c..214f77ddb 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -96425,6 +96425,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + +