DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
daqconf::GraphBuilder Class Reference

#include <GraphBuilder.hpp>

Collaboration diagram for daqconf::GraphBuilder:
[legend]

Classes

struct  VertexLabel
struct  EdgeLabel
struct  EnhancedObject

Public Types

enum class  ObjectKind {
  kUndefined , kSession , kSegment , kApplication ,
  kModule , kIncomingExternal , kOutgoingExternal
}
using ConfigObject = dunedaq::conffwk::ConfigObject
using Graph_t = boost::adjacency_list<boost::vecS, boost::vecS, boost::bidirectionalS, VertexLabel, EdgeLabel>
using Edge_t = boost::graph_traits<Graph_t>::edge_descriptor
using Vertex_t = boost::graph_traits<Graph_t>::vertex_descriptor

Public Member Functions

 GraphBuilder (const std::string &oksfilename, const std::string &sessionname)
void construct_graph (std::string root_obj_uid)
void write_graph (const std::string &outputfilename) const
 GraphBuilder (const GraphBuilder &)=delete
 GraphBuilder (GraphBuilder &&)=delete
GraphBuilder & operator= (const GraphBuilder &)=delete
GraphBuilder & operator= (GraphBuilder &&)=delete

Private Member Functions

void find_candidate_objects ()
std::vector< dunedaq::conffwk::ConfigObject > find_child_objects (const ConfigObject &parent_obj)
void calculate_graph (const std::string &root_obj_uid)
void find_objects_and_connections (const ConfigObject &object)
void calculate_network_connections ()

Private Attributes

const std::string m_oksfilename
dunedaq::conffwk::Configuration * m_confdb
const std::unordered_map< ObjectKind, std::vector< std::string > > m_included_classes
std::unordered_map< std::string, EnhancedObject > m_objects_for_graph
std::unordered_map< std::string, std::vector< std::string > > m_incoming_connections
std::unordered_map< std::string, std::vector< std::string > > m_outgoing_connections
Graph_t m_graph
ObjectKind m_root_object_kind
dunedaq::confmodel::Session * m_session
std::string m_session_name
std::vector< std::string > m_ignored_application_uids
std::vector< ConfigObject > m_all_objects
std::vector< ConfigObject > m_candidate_objects

Detailed Description

Definition at line 43 of file GraphBuilder.hpp.

Member Typedef Documentation

◆ ConfigObject

◆ Edge_t

using daqconf::GraphBuilder::Edge_t = boost::graph_traits<Graph_t>::edge_descriptor

Definition at line 55 of file GraphBuilder.hpp.

◆ Graph_t

using daqconf::GraphBuilder::Graph_t = boost::adjacency_list<boost::vecS, boost::vecS, boost::bidirectionalS, VertexLabel, EdgeLabel>

Definition at line 54 of file GraphBuilder.hpp.

◆ Vertex_t

using daqconf::GraphBuilder::Vertex_t = boost::graph_traits<Graph_t>::vertex_descriptor

Definition at line 56 of file GraphBuilder.hpp.

Member Enumeration Documentation

◆ ObjectKind

Enumerator
kUndefined 
kSession 
kSegment 
kApplication 
kModule 
kIncomingExternal 
kOutgoingExternal 

Definition at line 58 of file GraphBuilder.hpp.

59 {
61 kSession,
62 kSegment,
63 kApplication,
64 kModule,
65 kIncomingExternal, // Object is meant to represent the outside world, not an actual component of the DAQ
66 kOutgoingExternal // ""
67 };
const int kUndefined
Definition Hit.hpp:15

Constructor & Destructor Documentation

◆ GraphBuilder() [1/3]

daqconf::GraphBuilder::GraphBuilder ( const std::string & oksfilename,
const std::string & sessionname )
explicit

Definition at line 54 of file GraphBuilder.cpp.

55 : m_oksfilename{ oksfilename }
56 , m_confdb{ nullptr }
57 , m_included_classes{ { ObjectKind::kSession, { "Session", "Segment", "Application" } },
58 { ObjectKind::kSegment, { "Segment", "Application" } },
59 { ObjectKind::kApplication, { "Application", "Module" } },
60 { ObjectKind::kModule, { "Module" } } }
62 , m_session{ nullptr }
63 , m_session_name{ sessionname }
64{
65
66 // Open the database represented by the OKS XML file
67
68 try {
69 m_confdb = new dunedaq::conffwk::Configuration("oksconflibs:" + m_oksfilename);
70 } catch (dunedaq::conffwk::Generic& exc) {
71 TLOG() << "Failed to load OKS database: " << exc << "\n";
72 throw exc;
73 }
74
75 // Get the session in the database
76 std::vector<ConfigObject> session_objects{};
77
78 m_confdb->get("Session", session_objects);
79
80 if (m_session_name == "") { // If no session name given, use the one-and-only session expected in the database
81 if (session_objects.size() == 1) {
82 m_session_name = session_objects[0].UID();
83 } else {
84 std::stringstream errmsg;
85 errmsg << "No Session instance name was provided, and since " << session_objects.size()
86 << " session instances were found in \"" << m_oksfilename << "\" this is an error";
87
88 throw daqconf::GeneralGraphToolError(ERS_HERE, errmsg.str());
89 }
90 } else { // session name provided by the user, let's make sure it's there
91 auto it =
92 std::ranges::find_if(session_objects, [&](const ConfigObject& obj) { return obj.UID() == m_session_name; });
93
94 if (it == session_objects.end()) {
95 std::stringstream errmsg;
96 errmsg << "Did not find Session instance \"" << m_session_name << "\" in \"" << m_oksfilename
97 << "\" and its includes";
98 throw daqconf::GeneralGraphToolError(ERS_HERE, errmsg.str());
99 }
100 }
101
102 // The following not-brief section of code is dedicated to
103 // determining which applications in the configuration are
104 // excluded
105
106 // First, we need the session object to check if an application
107 // has been excluded
108
109 // Note the "const_cast" is needed since "m_confdb->get"
110 // returns a const pointer, but since m_session is a member needed
111 // by multiple functions and can't be determined until after we've
112 // opened the database and found the session, we need to change
113 // its initial value here. Once this is done, it shouldn't be
114 // changed again.
115
116 m_session = const_cast<dunedaq::confmodel::Session*>( // NOLINT
117 m_confdb->get<dunedaq::confmodel::Session>(m_session_name));
118
119 if (!m_session) {
120 std::stringstream errmsg;
121 errmsg << "Unable to get session with UID \"" << m_session_name << "\"";
122 throw daqconf::GeneralGraphToolError(ERS_HERE, errmsg.str());
123 }
124
125 std::vector<ConfigObject> every_object_deriving_from_class{}; // Includes objects of the class itself
126 std::vector<ConfigObject> objects_of_class{}; // A subset of every_object_deriving_from_class
127
128 // m_confdb->superclasses() returns a conffwk::fmap; see the conffwk package for details
129
130 auto classnames = m_confdb->superclasses() | std::views::keys |
131 std::views::transform([](const auto& ptr_to_class_name) { return *ptr_to_class_name; });
132
133 for (const auto& classname : classnames) {
134
135 every_object_deriving_from_class.clear();
136 objects_of_class.clear();
137
138 m_confdb->get(classname, every_object_deriving_from_class);
139
140 std::ranges::copy_if(every_object_deriving_from_class,
141 std::back_inserter(objects_of_class),
142 [&classname](const ConfigObject& obj) { return obj.class_name() == classname; });
143
144 std::ranges::copy(objects_of_class, std::back_inserter(m_all_objects));
145
146 if (classname.find("Application") != std::string::npos) { // DFApplication, ReadoutApplication, etc.
147 for (const auto& appobj : objects_of_class) {
148
149 auto daqapp = m_confdb->get<dunedaq::appmodel::SmartDaqApplication>(appobj.UID());
150
151 if (daqapp) {
152
153 auto res = daqapp->cast<dunedaq::confmodel::ExcludableEntity>();
154
155 if (res && res->is_excluded(*m_session)) {
156 m_ignored_application_uids.push_back(appobj.UID());
157 TLOG() << "Skipping excluded application " << appobj.UID() << "@" << daqapp->class_name();
158 continue;
159 }
160 } else {
161 TLOG(TLVL_DEBUG) << "Skipping non-SmartDaqApplication " << appobj.UID() << "@" << appobj.class_name();
162 m_ignored_application_uids.push_back(appobj.UID());
163 }
164 }
165 }
166 }
167}
#define ERS_HERE
dunedaq::confmodel::Session * m_session
std::vector< ConfigObject > m_all_objects
const std::unordered_map< ObjectKind, std::vector< std::string > > m_included_classes
dunedaq::conffwk::Configuration * m_confdb
std::vector< std::string > m_ignored_application_uids
const std::string m_oksfilename
dunedaq::conffwk::ConfigObject ConfigObject
const conffwk::fmap< conffwk::fset > & superclasses() const noexcept
void get(const std::string &class_name, const std::string &id, ConfigObject &object, unsigned long rlevel=0, const std::vector< std::string > *rclasses=0)
Get object by class name and object id (multi-thread safe).
#define TLOG(...)
Definition macro.hpp:21

◆ GraphBuilder() [2/3]

daqconf::GraphBuilder::GraphBuilder ( const GraphBuilder & )
delete

◆ GraphBuilder() [3/3]

daqconf::GraphBuilder::GraphBuilder ( GraphBuilder && )
delete

Member Function Documentation

◆ calculate_graph()

void daqconf::GraphBuilder::calculate_graph ( const std::string & root_obj_uid)
private

Definition at line 187 of file GraphBuilder.cpp.

188{
189
190 // To start, get the session / segments / applications in the
191 // session by setting up a temporary graph with the session as its
192 // root. This way we can check to see if the actual requested root
193 // object lies within the session in question.
194
195 auto true_root_object_kind = m_root_object_kind;
198
199 auto it_session =
200 std::ranges::find_if(m_all_objects, [&](const ConfigObject& obj) { return obj.UID() == m_session_name; });
201
202 find_objects_and_connections(*it_session);
203
204 if (!m_objects_for_graph.contains(root_obj_uid)) {
205 std::stringstream errmsg;
206 errmsg << "Unable to find requested object \"" << root_obj_uid << "\" in session \"" << m_session_name << "\"";
207 throw daqconf::GeneralGraphToolError(ERS_HERE, errmsg.str());
208 }
209
210 // Since we used our first call to find_objects_and_connections
211 // only as a fact-finding mission, reset the containers it filled
212
213 m_objects_for_graph.clear();
216
217 m_candidate_objects.clear();
218
219 m_root_object_kind = true_root_object_kind;
221
222 bool found = false;
223 for (auto& obj : m_candidate_objects) {
224 if (obj.UID() == root_obj_uid) {
225 found = true;
226 find_objects_and_connections(obj); // Put differently, "find what will make up the vertices and edges"
227 break;
228 }
229 }
230
231 assert(found);
232
233 calculate_network_connections(); // Put differently, "find the edges between the vertices"
234}
void find_objects_and_connections(const ConfigObject &object)
std::unordered_map< std::string, std::vector< std::string > > m_incoming_connections
std::unordered_map< std::string, std::vector< std::string > > m_outgoing_connections
std::unordered_map< std::string, EnhancedObject > m_objects_for_graph
std::vector< ConfigObject > m_candidate_objects

◆ calculate_network_connections()

void daqconf::GraphBuilder::calculate_network_connections ( )
private

Definition at line 237 of file GraphBuilder.cpp.

238{
239
240 // Will use "incoming_matched" and "outgoing_matched" to keep
241 // track of incoming and outgoing connections which don't get
242 // matched, i.e. would terminate external to the graph
243
244 std::vector<std::string> incoming_matched;
245 std::vector<std::string> outgoing_matched;
246
247 for (auto& incoming : m_incoming_connections) {
248
249 std::regex incoming_pattern(incoming.first);
250
251 for (auto& outgoing : m_outgoing_connections) {
252
253 std::regex outgoing_pattern(outgoing.first);
254
255 bool match = false;
256
257 if (incoming.first == outgoing.first) {
258 match = true;
259 } else if (incoming.first.find(".*") != std::string::npos) {
260 if (std::regex_match(outgoing.first, incoming_pattern)) {
261 match = true;
262 }
263 } else if (outgoing.first.find(".*") != std::string::npos) {
264 if (std::regex_match(incoming.first, outgoing_pattern)) {
265 match = true;
266 }
267 }
268
269 if (match) {
270
271 bool low_level_plot =
273
274 for (auto& receiver : incoming.second) {
275 for (auto& sender : outgoing.second) {
276
277 // We just want to plot applications sending to other
278 // applications and queues sending to other
279 // queues. Showing, e.g., a queue directly sending to
280 // some other application via a network connection makes
281 // the plot too busy.
282
283 if (!m_objects_for_graph.contains(sender) || !m_objects_for_graph.contains(receiver)) {
284 continue;
285 } else if (m_objects_for_graph.at(sender).kind != m_objects_for_graph.at(receiver).kind) {
286 continue;
287 } else if (low_level_plot && incoming.first.find("NetworkConnection") != std::string::npos) {
288
289 // Don't want to directly link modules in an
290 // application if the data is transferred over network
291 continue;
292 }
293
294 if (!low_level_plot || incoming.first.find(".*") == std::string::npos) {
295 if (std::ranges::find(incoming_matched, incoming.first) == incoming_matched.end()) {
296 incoming_matched.push_back(incoming.first);
297 }
298 }
299
300 if (!low_level_plot || outgoing.first.find(".*") == std::string::npos) {
301 if (std::ranges::find(outgoing_matched, outgoing.first) == outgoing_matched.end()) {
302 outgoing_matched.push_back(outgoing.first);
303 }
304 }
305
306 const EnhancedObject::ReceivingInfo receiving_info{ incoming.first, receiver };
307
308 auto res = std::ranges::find(m_objects_for_graph.at(sender).receiving_object_infos, receiving_info);
309 if (res == m_objects_for_graph.at(sender).receiving_object_infos.end()) {
310 m_objects_for_graph.at(sender).receiving_object_infos.push_back(receiving_info);
311 }
312 }
313 }
314 }
315 }
316 }
317
318 auto incoming_unmatched =
319 m_incoming_connections | std::views::keys | std::views::filter([&incoming_matched](auto& connection) {
320 return std::ranges::find(incoming_matched, connection) == incoming_matched.end();
321 });
322
323 auto included_classes = m_included_classes.at(m_root_object_kind);
324
325 for (auto& incoming_conn : incoming_unmatched) {
326
328 const std::string incoming_vertex_name = incoming_conn;
329
330 // Find the connections appropriate to the granularity level of this graph
331 for (auto& receiving_object_name : m_incoming_connections[incoming_conn]) {
332
333 if (!m_objects_for_graph.contains(receiving_object_name)) {
334 continue;
335 }
336
337 if (std::ranges::find(included_classes, "Module") != included_classes.end()) {
338 if (m_objects_for_graph.at(receiving_object_name).kind == ObjectKind::kModule) {
339 external_obj.receiving_object_infos.push_back({ incoming_conn, receiving_object_name });
340 }
341 } else if (std::ranges::find(included_classes, "Application") != included_classes.end()) {
342 if (m_objects_for_graph.at(receiving_object_name).kind == ObjectKind::kApplication) {
343 external_obj.receiving_object_infos.push_back({ incoming_conn, receiving_object_name });
344 }
345 }
346 }
347
348 m_objects_for_graph.insert({ incoming_vertex_name, external_obj });
349 }
350
351 auto outgoing_unmatched =
352 m_outgoing_connections | std::views::keys | std::views::filter([&outgoing_matched](auto& connection) {
353 return std::ranges::find(outgoing_matched, connection) == outgoing_matched.end();
354 });
355
356 for (auto& outgoing_conn : outgoing_unmatched) {
357
359 const std::string outgoing_vertex_name = outgoing_conn;
360
361 // Find the connections appropriate to the granularity level of this graph
362 for (auto& sending_object_name : m_outgoing_connections[outgoing_conn]) {
363
364 if (!m_objects_for_graph.contains(sending_object_name)) {
365 continue;
366 }
367
368 if (std::ranges::find(included_classes, "Module") != included_classes.end()) {
369 if (m_objects_for_graph.at(sending_object_name).kind == ObjectKind::kModule) {
370 m_objects_for_graph.at(sending_object_name)
371 .receiving_object_infos.push_back({ outgoing_conn, outgoing_vertex_name });
372 }
373 } else if (std::ranges::find(included_classes, "Application") != included_classes.end()) {
374 if (m_objects_for_graph.at(sending_object_name).kind == ObjectKind::kApplication) {
375 m_objects_for_graph.at(sending_object_name)
376 .receiving_object_infos.push_back({ outgoing_conn, outgoing_vertex_name });
377 }
378 }
379 }
380
381 m_objects_for_graph.insert({ outgoing_vertex_name, external_obj });
382 }
383}
dunedaq::conffwk::relationship_t match(T const &, T const &)

◆ construct_graph()

void daqconf::GraphBuilder::construct_graph ( std::string root_obj_uid)

Definition at line 514 of file GraphBuilder.cpp.

515{
516
517 if (root_obj_uid == "") {
518 root_obj_uid = m_session_name;
519 }
520
521 // Next several lines just mean "tell me the class type of the root object in the config plot's graph"
522
523 auto class_names_view =
524 m_all_objects | std::views::filter([root_obj_uid](const ConfigObject& obj) { return obj.UID() == root_obj_uid; }) |
525 std::views::transform([](const ConfigObject& obj) { return obj.class_name(); });
526
527 if (std::ranges::distance(class_names_view) != 1) {
528 std::stringstream errmsg;
529 errmsg << "Failed to find instance of desired root object \"" << root_obj_uid << "\"";
530 throw daqconf::GeneralGraphToolError(ERS_HERE, errmsg.str());
531 }
532
533 const std::string& root_obj_class_name = *class_names_view.begin();
534
535 m_root_object_kind = get_object_kind(root_obj_class_name);
536
537 calculate_graph(root_obj_uid);
538
539 for (auto& enhanced_object : m_objects_for_graph | std::views::values) {
540
541 if (enhanced_object.kind == ObjectKind::kIncomingExternal) {
542 enhanced_object.vertex_in_graph = boost::add_vertex(VertexLabel("O", ""), m_graph);
543 } else if (enhanced_object.kind == ObjectKind::kOutgoingExternal) {
544 enhanced_object.vertex_in_graph = boost::add_vertex(VertexLabel("X", ""), m_graph);
545 } else {
546 auto& obj = enhanced_object.config_object;
547 enhanced_object.vertex_in_graph = boost::add_vertex(VertexLabel(obj.UID(), obj.class_name()), m_graph);
548 }
549 }
550
551 for (auto& parent_obj : m_objects_for_graph | std::views::values) {
552 for (auto& child_obj_name : parent_obj.child_object_names) {
553 boost::add_edge(parent_obj.vertex_in_graph,
554 m_objects_for_graph.at(child_obj_name).vertex_in_graph,
555 { "" }, // No label for an edge which just describes "ownership" rather than dataflow
556 m_graph);
557 }
558 }
559
560 for (auto& possible_sender_object : m_objects_for_graph | std::views::values) {
561 for (auto& receiver_info : possible_sender_object.receiving_object_infos) {
562
563 // If we're plotting at the level of a session or segment,
564 // show the connections as between applications; if we're
565 // doing this for a single application, show them entering and
566 // exiting the individual modules
567
569 if (m_objects_for_graph.at(receiver_info.receiver_label).kind == ObjectKind::kModule) {
570 continue;
571 }
572 }
573
575 if (m_objects_for_graph.at(receiver_info.receiver_label).kind == ObjectKind::kApplication) {
576 continue;
577 }
578 }
579
580 boost::add_edge(possible_sender_object.vertex_in_graph,
581 m_objects_for_graph.at(receiver_info.receiver_label).vertex_in_graph,
582 { receiver_info.connection_name },
583 m_graph)
584 .first;
585 }
586 }
587}
void calculate_graph(const std::string &root_obj_uid)
constexpr GraphBuilder::ObjectKind get_object_kind(const std::string &class_name)

◆ find_candidate_objects()

void daqconf::GraphBuilder::find_candidate_objects ( )
private

Definition at line 170 of file GraphBuilder.cpp.

171{
172
173 m_candidate_objects.clear();
174
175 for (const auto& obj : m_all_objects) {
176 for (const auto& classname : this->m_included_classes.at(m_root_object_kind)) {
177
178 if (obj.class_name().find(classname) != std::string::npos &&
179 std::ranges::find(m_ignored_application_uids, obj.UID()) == m_ignored_application_uids.end()) {
180 m_candidate_objects.emplace_back(obj);
181 }
182 }
183 }
184}

◆ find_child_objects()

std::vector< dunedaq::conffwk::ConfigObject > daqconf::GraphBuilder::find_child_objects ( const ConfigObject & parent_obj)
nodiscardprivate

Definition at line 590 of file GraphBuilder.cpp.

591{
592
593 std::vector<ConfigObject> connected_objects{};
594
595 dunedaq::conffwk::class_t classdef = m_confdb->get_class_info(parent_obj.class_name(), false);
596
597 for (const dunedaq::conffwk::relationship_t& relationship : classdef.p_relationships) {
598
599 // The ConfigObject::get(...) function doesn't have a
600 // const-qualifier on it for no apparent good reason; we need
601 // this cast in order to call it
602
603 auto parent_obj_casted = const_cast<ConfigObject&>(parent_obj); // NOLINT
604
605 if (relationship.p_cardinality == dunedaq::conffwk::only_one ||
607 ConfigObject connected_obj{};
608 parent_obj_casted.get(relationship.p_name, connected_obj);
609 connected_objects.push_back(connected_obj);
610 } else {
611 std::vector<ConfigObject> connected_objects_in_relationship{};
612 parent_obj_casted.get(relationship.p_name, connected_objects_in_relationship);
613 connected_objects.insert(
614 connected_objects.end(), connected_objects_in_relationship.begin(), connected_objects_in_relationship.end());
615 }
616 }
617
618 return connected_objects;
619}
void get(const std::string &name, T &value)
Get value of object's attribute or relationship.
const dunedaq::conffwk::class_t & get_class_info(const std::string &class_name, bool direct_only=false)
The method provides access to description of class.
const std::vector< relationship_t > p_relationships
Definition Schema.hpp:162

◆ find_objects_and_connections()

void daqconf::GraphBuilder::find_objects_and_connections ( const ConfigObject & object)
private

Definition at line 386 of file GraphBuilder.cpp.

387{
388
389 EnhancedObject starting_object{ object, get_object_kind(object.class_name()) };
390
391 // If we've got a session or a segment, look at its OKS-relations,
392 // and recursively process those relation objects which are on the
393 // candidates list and haven't already been processed
394
395 if (starting_object.kind == ObjectKind::kSession || starting_object.kind == ObjectKind::kSegment) {
396
397 for (auto& child_object : find_child_objects(starting_object.config_object)) {
398
399 if (std::ranges::find(m_candidate_objects, child_object) != m_candidate_objects.end()) {
400 find_objects_and_connections(child_object);
401 starting_object.child_object_names.push_back(child_object.UID());
402 }
403 }
404 } else if (starting_object.kind == ObjectKind::kApplication) {
405
406 // If we've got an application object, try to determine what
407 // modules are in it and what their connections are. Recursively
408 // process the modules, and then add connection info to class-wide
409 // member maps to calculate edges corresponding to the connections
410 // for the plotted graph later
411
412 dunedaq::conffwk::Configuration* local_database{ nullptr };
413
414 try {
415 local_database = new dunedaq::conffwk::Configuration("oksconflibs:" + m_oksfilename);
416 } catch (dunedaq::conffwk::Generic& exc) {
417 TLOG() << "Failed to load OKS database: " << exc << "\n";
418 throw exc;
419 }
420
421 auto daqapp = local_database->get<dunedaq::appmodel::SmartDaqApplication>(object.UID());
422 if (daqapp) {
423 auto local_session = const_cast<dunedaq::confmodel::Session*>( // NOLINT
424 local_database->get<dunedaq::confmodel::Session>(m_session_name));
425
426 auto helper = std::make_shared<dunedaq::appmodel::ConfigurationHelper>(local_session);
427 daqapp->generate_modules(helper);
428 auto modules = daqapp->get_modules();
429
430 std::vector<std::string> allowed_conns{};
431
433 allowed_conns = { "NetworkConnection" };
435 allowed_conns = { "NetworkConnection", "Queue", "QueueWithSourceId", "DataMoveCallbackConf" };
436 }
437
438 for (const auto& module : modules) {
439
440 for (auto in : module->get_inputs()) {
441
442 // Elsewhere in the code it'll be useful to know if the
443 // connection is a network or a queue, so include the
444 // class name in the std::string key
445
446 std::string key = in->config_object().UID() + "@" + in->config_object().class_name();
447
448 if (in->config_object().class_name() == "NetworkConnection") {
449 auto innc = in->cast<dunedaq::confmodel::NetworkConnection>();
450 key += "@" + innc->get_connection_type();
451 }
452
453 if (std::ranges::find(allowed_conns, in->config_object().class_name()) != allowed_conns.end()) {
454 m_incoming_connections[key].push_back(object.UID());
455 m_incoming_connections[key].push_back(module->UID());
456 }
457 }
458
459 for (auto out : module->get_outputs()) {
460
461 std::string key = out->config_object().UID() + "@" + out->config_object().class_name();
462
463 if (out->config_object().class_name() == "NetworkConnection") {
464 auto outnc = out->cast<dunedaq::confmodel::NetworkConnection>();
465 key += "@" + outnc->get_connection_type();
466 }
467
468 if (std::ranges::find(allowed_conns, out->config_object().class_name()) != allowed_conns.end()) {
469 m_outgoing_connections[key].push_back(object.UID());
470 m_outgoing_connections[key].push_back(module->UID());
471 }
472 }
473
474 // Look for DataMoveCallbackConfs
475 auto datareader = module->cast<dunedaq::appmodel::DataReaderModule>();
476 auto datahandler = module->cast<dunedaq::appmodel::DataHandlerModule>();
477
478 if (datareader != nullptr) {
479 for (auto& out : datareader->get_raw_data_callbacks()) {
480 const std::string key = out->config_object().UID() + "@" + out->config_object().class_name();
481 if (std::ranges::find(allowed_conns, out->config_object().class_name()) != allowed_conns.end()) {
482 m_outgoing_connections[key].push_back(object.UID());
483 m_outgoing_connections[key].push_back(module->UID());
484 }
485 }
486 }
487 if (datahandler != nullptr) {
488 auto in = datahandler->get_raw_data_callback();
489 if (in != nullptr) {
490 const std::string key = in->config_object().UID() + "@" + in->config_object().class_name();
491
492 if (std::ranges::find(allowed_conns, in->config_object().class_name()) != allowed_conns.end()) {
493 m_incoming_connections[key].push_back(object.UID());
494 m_incoming_connections[key].push_back(module->UID());
495 }
496 }
497 }
498
499 if (std::ranges::find(m_included_classes.at(m_root_object_kind), "Module") !=
501 find_objects_and_connections(module->config_object());
502 starting_object.child_object_names.push_back(module->UID());
503 }
504 }
505 }
506 }
507
508 assert(!m_objects_for_graph.contains(object.UID()));
509
510 m_objects_for_graph.insert({ object.UID(), starting_object });
511}
std::vector< dunedaq::conffwk::ConfigObject > find_child_objects(const ConfigObject &parent_obj)
module(name, schema, other_dals=[], backend='oksconflibs', db=None)
Definition dal.py:695
Changes could not be no object was Database change was not no object was not An invalid qmodel index has been Config layer returned null reference for object
FELIX Initialization std::string initerror FELIX queue timed out

◆ operator=() [1/2]

GraphBuilder & daqconf::GraphBuilder::operator= ( const GraphBuilder & )
delete

◆ operator=() [2/2]

GraphBuilder & daqconf::GraphBuilder::operator= ( GraphBuilder && )
delete

◆ write_graph()

void daqconf::GraphBuilder::write_graph ( const std::string & outputfilename) const

Definition at line 622 of file GraphBuilder.cpp.

623{
624
625 std::stringstream outputstream;
626
627 boost::write_graphviz(outputstream,
628 m_graph,
629 boost::make_label_writer(boost::get(&GraphBuilder::VertexLabel::displaylabel, m_graph)),
630 boost::make_label_writer(boost::get(&GraphBuilder::EdgeLabel::displaylabel, m_graph)));
631
632 // It's arguably hacky to edit the DOT code generated by
633 // boost::write_graphviz after the fact to give vertices colors,
634 // but the fact is that the color-assigning logic in Boost's graph
635 // library is so messy and clumsy that this is a worthwhile
636 // tradeoff
637
638 struct VertexStyle
639 {
640 const std::string shape;
641 const std::string color;
642 };
643
644 const std::unordered_map<ObjectKind, VertexStyle> vertex_styles{ { ObjectKind::kSession, { "octagon", "black" } },
645 { ObjectKind::kSegment, { "hexagon", "brown" } },
646 { ObjectKind::kApplication, { "pentagon", "blue" } },
647 { ObjectKind::kModule, { "rectangle", "red" } } };
648
649 std::string dotfile_slurped = outputstream.str();
650 std::vector<std::string> legend_entries{
651 "legendGA [label=<<font color=\"black\"><b><i>&#10230; Network Connection</i></b></font>>, shape=plaintext];",
652 "legendGB [label=<<font color=\"blue\"><b><i>&#10230; Pub/Sub Network</i></b></font>>, shape=plaintext];"
653 };
654 std::vector<std::string> internal_legend_entries{
655 "legendGC [label=<<font color=\"green\"><b><i>&#10230; Data Move Callback</i></b></font>>, shape=plaintext];",
656 "legendGD [label=<<font color=\"red\"><b><i>&#10230; Queue</i></b></font>>, shape=plaintext];",
657 "legendGE [label=<<font color=\"orange\"><b><i>&#10230; Queue w/ Source ID</i></b></font>>, shape=plaintext];"
658 };
659 bool internal_legend_added = false;
660 std::vector<std::string> legend_ordering_code{};
661
662 for (auto& eo : m_objects_for_graph | std::views::values) {
663
664 std::stringstream vertexstr{};
665 std::stringstream legendstr{};
666 std::stringstream labelstringstr{};
667 size_t insertion_location{ 0 };
668
669 auto calculate_insertion_location = [&]() {
670 labelstringstr << "label=\"" << eo.config_object.UID() << "\n";
671 insertion_location = dotfile_slurped.find(labelstringstr.str());
672 assert(insertion_location != std::string::npos);
673 return insertion_location;
674 };
675
676 // TODO: John Freeman (jcfree@fnal.gov), Sep-17-2024
677
678 // Switch to std::format for line construction rather than
679 // std::stringstream when we switch to a gcc version which
680 // supports it
681
682 auto add_vertex_info = [&]() {
683 vertexstr << "shape=" << vertex_styles.at(eo.kind).shape << ", color=" << vertex_styles.at(eo.kind).color
684 << ", fontcolor=" << vertex_styles.at(eo.kind).color << ", ";
685 dotfile_slurped.insert(calculate_insertion_location(), vertexstr.str());
686 };
687
688 auto add_legend_entry = [&](char letter, const std::string objkind) {
689 legendstr << "legend" << letter << " [label=<<font color=\"" << vertex_styles.at(eo.kind).color << "\"><b><i>"
690 << vertex_styles.at(eo.kind).color << ": " << objkind
691 << "</i></b></font>>, shape=plaintext, color=" << vertex_styles.at(eo.kind).color
692 << ", fontcolor=" << vertex_styles.at(eo.kind).color << "];";
693 };
694
695 // Note that the seemingly arbitrary single characters added
696 // after "legend" aren't just to uniquely identify each entry in
697 // the legend, it's also so that when the entries are sorted
698 // alphabetically they'll appear in the correct order
699
700 switch (eo.kind) {
702 add_vertex_info();
703 add_legend_entry('A', "session");
704 break;
706 add_vertex_info();
707 add_legend_entry('B', "segment");
708 break;
710 add_vertex_info();
711 add_legend_entry('C', "application");
712 break;
714 add_vertex_info();
715 add_legend_entry('D', "DAQModule");
716 if (!internal_legend_added) {
717 legend_entries.insert(legend_entries.end(), internal_legend_entries.begin(), internal_legend_entries.end());
718 internal_legend_added = true;
719 }
720 break;
722 legendstr
723 << "legendE [label=<<font color=\"black\">O:<b><i> External Data Source</i></b></font>>, shape=plaintext];";
724 break;
726 legendstr
727 << "legendF [label=<<font color=\"black\">X:<b><i> External Data Sink</i></b></font>>, shape=plaintext];";
728 break;
729 default:
730 assert(false);
731 }
732
733 if (std::ranges::find(legend_entries, legendstr.str()) == legend_entries.end()) {
734 legend_entries.emplace_back(legendstr.str());
735 }
736 }
737
738 std::ranges::sort(legend_entries);
739
740 // We have the line-by-line entries containing the labels in our
741 // legend and their colors, but more DOT code is needed in order
742 // to show the labels in order. E.g., if we have:
743
744 // legendA [label=<<font color="blue">Blue: Segment</font>>, shape=box, color=blue, fontcolor=blue];
745 // legendB [label=<<font color="red">Red: Application</font>>, shape=box, color=red, fontcolor=red];
746 //
747 // Then we'll also need
748 //
749 // legendA -> legendB [style=invis];
750
751 auto legend_tokens = legend_entries | std::views::transform([](const std::string& line) {
752 return line.substr(0, line.find(' ')); // i.e., grab the first word on the line
753 });
754
755 auto it = legend_tokens.begin();
756 for (auto next_it = std::next(it); next_it != legend_tokens.end(); ++it, ++next_it) {
757 std::stringstream astr{};
758 astr << " " << *it << " -> " << *next_it << " [style=invis];";
759 legend_ordering_code.push_back(astr.str());
760 }
761
762 constexpr int chars_to_last_brace = 2;
763 auto last_brace_iter = dotfile_slurped.end() - chars_to_last_brace;
764 assert(*last_brace_iter == '}');
765 size_t last_brace_loc = last_brace_iter - dotfile_slurped.begin();
766
767 std::string legend_code{};
768 legend_code += "\n\n\n";
769
770 for (const auto& l : legend_entries) {
771 legend_code += l + "\n";
772 }
773
774 legend_code += "\n\n\n";
775
776 for (const auto& l : legend_ordering_code) {
777 legend_code += l + "\n";
778 }
779
780 dotfile_slurped.insert(last_brace_loc, legend_code);
781
782 // Take advantage of the fact that the edges describing ownership
783 // rather than data flow (e.g., hsi-segment owning hsi-01, the
784 // FakeHSIApplication) have null labels in order to turn them into
785 // arrow-free dotted lines
786
787 const std::string unlabeled_edge = "label=\"\"";
788 const std::string edge_modifier = ", style=\"dotted\", arrowhead=\"none\"";
789
790 size_t pos = 0;
791 while ((pos = dotfile_slurped.find(unlabeled_edge, pos)) != std::string::npos) {
792 dotfile_slurped.replace(pos, unlabeled_edge.length(), unlabeled_edge + edge_modifier);
793 pos += (unlabeled_edge + edge_modifier).length();
794 }
795
796 // Replace the connection types with color information
797 std::vector<std::pair<std::string, std::string>> connection_colors = {
798 { "@NetworkConnection@kSendRecv\"", "\", color=black" },
799 { "@NetworkConnection@kPubSub\"", "\", color=blue" },
800 { "@QueueWithSourceId\"", "\", color=orange" },
801 { "@Queue\"", "\", color=red" },
802 { "@DataMoveCallbackConf\"", "\", color=green" }
803 };
804 for (auto& color_pair : connection_colors) {
805 auto conn_type = color_pair.first;
806 auto color_info = color_pair.second;
807 pos = 0;
808 while ((pos = dotfile_slurped.find(conn_type, pos)) != std::string::npos) {
809 dotfile_slurped.replace(pos, conn_type.length(), color_info);
810 pos += color_info.length();
811 }
812 }
813
814 // And now with all the edits made to the contents of the DOT code, write it to file
815
816 std::ofstream outputfile;
817 outputfile.open(outputfilename);
818
819 if (outputfile.is_open()) {
820 outputfile << dotfile_slurped.c_str();
821 } else {
822 std::stringstream errmsg;
823 errmsg << "Unable to open requested file \"" << outputfilename << "\" for writing";
824 throw daqconf::GeneralGraphToolError(ERS_HERE, errmsg.str());
825 }
826}

Member Data Documentation

◆ m_all_objects

std::vector<ConfigObject> daqconf::GraphBuilder::m_all_objects
private

Definition at line 155 of file GraphBuilder.hpp.

◆ m_candidate_objects

std::vector<ConfigObject> daqconf::GraphBuilder::m_candidate_objects
private

Definition at line 156 of file GraphBuilder.hpp.

◆ m_confdb

dunedaq::conffwk::Configuration* daqconf::GraphBuilder::m_confdb
private

Definition at line 138 of file GraphBuilder.hpp.

◆ m_graph

Graph_t daqconf::GraphBuilder::m_graph
private

Definition at line 147 of file GraphBuilder.hpp.

◆ m_ignored_application_uids

std::vector<std::string> daqconf::GraphBuilder::m_ignored_application_uids
private

Definition at line 153 of file GraphBuilder.hpp.

◆ m_included_classes

const std::unordered_map<ObjectKind, std::vector<std::string> > daqconf::GraphBuilder::m_included_classes
private

Definition at line 140 of file GraphBuilder.hpp.

◆ m_incoming_connections

std::unordered_map<std::string, std::vector<std::string> > daqconf::GraphBuilder::m_incoming_connections
private

Definition at line 144 of file GraphBuilder.hpp.

◆ m_objects_for_graph

std::unordered_map<std::string, EnhancedObject> daqconf::GraphBuilder::m_objects_for_graph
private

Definition at line 142 of file GraphBuilder.hpp.

◆ m_oksfilename

const std::string daqconf::GraphBuilder::m_oksfilename
private

Definition at line 137 of file GraphBuilder.hpp.

◆ m_outgoing_connections

std::unordered_map<std::string, std::vector<std::string> > daqconf::GraphBuilder::m_outgoing_connections
private

Definition at line 145 of file GraphBuilder.hpp.

◆ m_root_object_kind

ObjectKind daqconf::GraphBuilder::m_root_object_kind
private

Definition at line 148 of file GraphBuilder.hpp.

◆ m_session

dunedaq::confmodel::Session* daqconf::GraphBuilder::m_session
private

Definition at line 150 of file GraphBuilder.hpp.

◆ m_session_name

std::string daqconf::GraphBuilder::m_session_name
private

Definition at line 151 of file GraphBuilder.hpp.


The documentation for this class was generated from the following files: