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  EdgeLabel
 
struct  EnhancedObject
 
struct  VertexLabel
 

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
 
GraphBuilderoperator= (const GraphBuilder &)=delete
 
GraphBuilderoperator= (GraphBuilder &&)=delete
 

Private Member Functions

void find_candidate_objects ()
 
std::vector< dunedaq::conffwk::ConfigObjectfind_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::Configurationm_confdb
 
const std::unordered_map< ObjectKind, std::vector< std::string > > m_included_classes
 
std::unordered_map< std::string, EnhancedObjectm_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::Sessionm_session
 
std::string m_session_name
 
std::vector< std::string > m_ignored_application_uids
 
std::vector< ConfigObjectm_all_objects
 
std::vector< ConfigObjectm_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.

Constructor & Destructor Documentation

◆ GraphBuilder() [1/3]

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

Definition at line 55 of file GraphBuilder.cpp.

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

◆ 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 188 of file GraphBuilder.cpp.

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

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

◆ construct_graph()

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

Definition at line 527 of file GraphBuilder.cpp.

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

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

◆ find_child_objects()

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

Definition at line 603 of file GraphBuilder.cpp.

604{
605
606 std::vector<ConfigObject> connected_objects{};
607
608 dunedaq::conffwk::class_t classdef = m_confdb->get_class_info(parent_obj.class_name(), false);
609
610 for (const dunedaq::conffwk::relationship_t& relationship : classdef.p_relationships) {
611
612 // The ConfigObject::get(...) function doesn't have a
613 // const-qualifier on it for no apparent good reason; we need
614 // this cast in order to call it
615
616 auto parent_obj_casted = const_cast<ConfigObject&>(parent_obj); // NOLINT
617
618 if (relationship.p_cardinality == dunedaq::conffwk::only_one ||
620 ConfigObject connected_obj{};
621 parent_obj_casted.get(relationship.p_name, connected_obj);
622 connected_objects.push_back(connected_obj);
623 } else {
624 std::vector<ConfigObject> connected_objects_in_relationship{};
625 parent_obj_casted.get(relationship.p_name, connected_objects_in_relationship);
626 connected_objects.insert(
627 connected_objects.end(), connected_objects_in_relationship.begin(), connected_objects_in_relationship.end());
628 }
629 }
630
631 return connected_objects;
632}
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:164

◆ find_objects_and_connections()

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

Definition at line 387 of file GraphBuilder.cpp.

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

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

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: