56 : m_oksfilename{ oksfilename }
59 { ObjectKind::kSegment, {
"Segment",
"Application" } },
60 { ObjectKind::kApplication, {
"Application",
"Module" } },
61 { ObjectKind::kModule, {
"Module" } } }
62 , m_root_object_kind{ ObjectKind::kUndefined }
63 , m_session{
nullptr }
64 , m_session_name{ sessionname }
72 TLOG() <<
"Failed to load OKS database: " << exc <<
"\n";
77 std::vector<ConfigObject> session_objects{};
79 m_confdb->get(
"Session", session_objects);
81 if (m_session_name ==
"") {
82 if (session_objects.size() == 1) {
83 m_session_name = session_objects[0].UID();
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";
89 throw daqconf::GeneralGraphToolError(
ERS_HERE, errmsg.str());
93 std::ranges::find_if(session_objects, [&](
const ConfigObject& obj) {
return obj.UID() == m_session_name; });
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());
121 std::stringstream errmsg;
122 errmsg <<
"Unable to get session with UID \"" << m_session_name <<
"\"";
123 throw daqconf::GeneralGraphToolError(
ERS_HERE, errmsg.str());
126 std::vector<ConfigObject> every_object_deriving_from_class{};
127 std::vector<ConfigObject> objects_of_class{};
131 auto classnames = m_confdb->superclasses() | std::views::keys |
132 std::views::transform([](
const auto& ptr_to_class_name) {
return *ptr_to_class_name; });
134 for (
const auto&
classname : classnames) {
136 every_object_deriving_from_class.clear();
137 objects_of_class.clear();
139 m_confdb->get(
classname, every_object_deriving_from_class);
141 std::ranges::copy_if(every_object_deriving_from_class,
142 std::back_inserter(objects_of_class),
145 std::ranges::copy(objects_of_class, std::back_inserter(m_all_objects));
147 if (
classname.find(
"Application") != std::string::npos) {
148 for (
const auto& appobj : objects_of_class) {
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();
162 TLOG(TLVL_DEBUG) <<
"Skipping non-SmartDaqApplication " << appobj.UID() <<
"@" << appobj.class_name();
163 m_ignored_application_uids.push_back(appobj.UID());
188GraphBuilder::calculate_graph(
const std::string& root_obj_uid)
196 auto true_root_object_kind = m_root_object_kind;
197 m_root_object_kind = ObjectKind::kSession;
198 find_candidate_objects();
201 std::ranges::find_if(m_all_objects, [&](
const ConfigObject& obj) {
return obj.UID() == m_session_name; });
203 find_objects_and_connections(*it_session);
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());
214 m_objects_for_graph.clear();
215 m_incoming_connections.clear();
216 m_outgoing_connections.clear();
218 m_candidate_objects.clear();
220 m_root_object_kind = true_root_object_kind;
221 find_candidate_objects();
224 for (
auto& obj : m_candidate_objects) {
225 if (obj.UID() == root_obj_uid) {
227 find_objects_and_connections(obj);
234 calculate_network_connections();
238GraphBuilder::calculate_network_connections()
245 std::vector<std::string> incoming_matched;
246 std::vector<std::string> outgoing_matched;
248 for (
auto& incoming : m_incoming_connections) {
250 std::regex incoming_pattern(incoming.first);
252 for (
auto& outgoing : m_outgoing_connections) {
254 std::regex outgoing_pattern(outgoing.first);
258 if (incoming.first == outgoing.first) {
260 }
else if (incoming.first.find(
".*") != std::string::npos) {
261 if (std::regex_match(outgoing.first, incoming_pattern)) {
264 }
else if (outgoing.first.find(
".*") != std::string::npos) {
265 if (std::regex_match(incoming.first, outgoing_pattern)) {
272 bool low_level_plot =
273 m_root_object_kind == ObjectKind::kApplication || m_root_object_kind == ObjectKind::kModule;
275 for (
auto& receiver : incoming.second) {
276 for (
auto& sender : outgoing.second) {
284 if (!m_objects_for_graph.contains(sender) || !m_objects_for_graph.contains(receiver)) {
286 }
else if (m_objects_for_graph.at(sender).kind != m_objects_for_graph.at(receiver).kind) {
288 }
else if (low_level_plot && incoming.first.find(
"NetworkConnection") != std::string::npos) {
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);
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);
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);
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();
324 auto included_classes = m_included_classes.at(m_root_object_kind);
326 for (
auto& incoming_conn : incoming_unmatched) {
329 const std::string incoming_vertex_name = incoming_conn;
332 for (
auto& receiving_object_name : m_incoming_connections[incoming_conn]) {
334 if (!m_objects_for_graph.contains(receiving_object_name)) {
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 });
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 });
349 m_objects_for_graph.insert({ incoming_vertex_name, external_obj });
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();
357 for (
auto& outgoing_conn : outgoing_unmatched) {
360 const std::string outgoing_vertex_name = outgoing_conn;
363 for (
auto& sending_object_name : m_outgoing_connections[outgoing_conn]) {
365 if (!m_objects_for_graph.contains(sending_object_name)) {
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 });
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 });
382 m_objects_for_graph.insert({ outgoing_vertex_name, external_obj });
396 if (starting_object.kind == ObjectKind::kSession || starting_object.kind == ObjectKind::kSegment) {
398 for (
auto& child_object : find_child_objects(starting_object.config_object)) {
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());
405 }
else if (starting_object.kind == ObjectKind::kApplication) {
418 TLOG() <<
"Failed to load OKS database: " << exc <<
"\n";
427 auto helper = std::make_shared<dunedaq::appmodel::ConfigurationHelper>(local_session);
428 daqapp->generate_modules(helper);
429 auto modules = daqapp->get_modules();
431 std::vector<std::string> allowed_conns{};
433 if (m_root_object_kind == ObjectKind::kSession || m_root_object_kind == ObjectKind::kSegment) {
434 allowed_conns = {
"NetworkConnection" };
435 }
else if (m_root_object_kind == ObjectKind::kApplication || m_root_object_kind == ObjectKind::kModule) {
436 allowed_conns = {
"NetworkConnection",
"Queue",
"QueueWithSourceId",
"DataMoveCallbackConf" };
439 for (
const auto& module : modules) {
441 for (
auto in : module->get_inputs()) {
447 std::string key = in->config_object().UID() +
"@" + in->config_object().class_name();
449 if (in->config_object().class_name() ==
"NetworkConnection") {
451 key +=
"@" + innc->get_connection_type();
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());
460 for (
auto out : module->get_outputs()) {
462 std::string key = out->config_object().UID() +
"@" + out->config_object().class_name();
464 if (out->config_object().class_name() ==
"NetworkConnection") {
466 key +=
"@" + outnc->get_connection_type();
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());
476 auto datareader =
module->cast<dunedaq::appmodel::DataReaderModule>();
477 auto datahandler =
module->cast<dunedaq::appmodel::DataHandlerModule>();
478 auto socketwriter =
module->cast<dunedaq::appmodel::SocketDataWriterModule>();
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());
489 if (datahandler !=
nullptr) {
490 auto in = datahandler->get_raw_data_callback();
492 const std::string key = in->config_object().UID() +
"@" + in->config_object().class_name();
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());
500 if (socketwriter !=
nullptr) {
501 auto in = socketwriter->get_raw_data_callback();
503 const std::string key = in->config_object().UID() +
"@" + in->config_object().class_name();
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());
512 if (std::ranges::find(m_included_classes.at(m_root_object_kind),
"Module") !=
513 m_included_classes.at(m_root_object_kind).end()) {
514 find_objects_and_connections(module->config_object());
515 starting_object.child_object_names.push_back(module->UID());
521 assert(!m_objects_for_graph.contains(
object.UID()));
523 m_objects_for_graph.insert({
object.UID(), starting_object });
527GraphBuilder::construct_graph(std::string root_obj_uid)
530 if (root_obj_uid ==
"") {
531 root_obj_uid = m_session_name;
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(); });
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());
546 const std::string& root_obj_class_name = *class_names_view.begin();
550 calculate_graph(root_obj_uid);
552 for (
auto& enhanced_object : m_objects_for_graph | std::views::values) {
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);
559 auto& obj = enhanced_object.config_object;
560 enhanced_object.vertex_in_graph = boost::add_vertex(
VertexLabel(obj.UID(), obj.class_name()), m_graph);
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,
573 for (
auto& possible_sender_object : m_objects_for_graph | std::views::values) {
574 for (
auto& receiver_info : possible_sender_object.receiving_object_infos) {
581 if (m_root_object_kind == ObjectKind::kSession || m_root_object_kind == ObjectKind::kSegment) {
582 if (m_objects_for_graph.at(receiver_info.receiver_label).kind == ObjectKind::kModule) {
587 if (m_root_object_kind == ObjectKind::kApplication || m_root_object_kind == ObjectKind::kModule) {
588 if (m_objects_for_graph.at(receiver_info.receiver_label).kind == ObjectKind::kApplication) {
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 },
635GraphBuilder::write_graph(
const std::string& outputfilename)
const
638 std::stringstream outputstream;
640 boost::write_graphviz(outputstream,
642 boost::make_label_writer(boost::get(&GraphBuilder::VertexLabel::displaylabel, m_graph)),
643 boost::make_label_writer(boost::get(&GraphBuilder::EdgeLabel::displaylabel, m_graph)));
653 const std::string shape;
654 const std::string color;
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" } } };
662 std::string dotfile_slurped = outputstream.str();
663 std::vector<std::string> legend_entries{
664 "legendGA [label=<<font color=\"black\"><b><i>⟶ Network Connection</i></b></font>>, shape=plaintext];",
665 "legendGB [label=<<font color=\"blue\"><b><i>⟶ Pub/Sub Network</i></b></font>>, shape=plaintext];"
667 std::vector<std::string> internal_legend_entries{
668 "legendGC [label=<<font color=\"green\"><b><i>⟶ Data Move Callback</i></b></font>>, shape=plaintext];",
669 "legendGD [label=<<font color=\"red\"><b><i>⟶ Queue</i></b></font>>, shape=plaintext];",
670 "legendGE [label=<<font color=\"orange\"><b><i>⟶ Queue w/ Source ID</i></b></font>>, shape=plaintext];"
672 bool internal_legend_added =
false;
673 std::vector<std::string> legend_ordering_code{};
675 for (
auto& eo : m_objects_for_graph | std::views::values) {
677 std::stringstream vertexstr{};
678 std::stringstream legendstr{};
679 std::stringstream labelstringstr{};
680 size_t insertion_location{ 0 };
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;
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());
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 <<
"];";
714 case ObjectKind::kSession:
716 add_legend_entry(
'A',
"session");
718 case ObjectKind::kSegment:
720 add_legend_entry(
'B',
"segment");
722 case ObjectKind::kApplication:
724 add_legend_entry(
'C',
"application");
726 case ObjectKind::kModule:
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;
736 case ObjectKind::kIncomingExternal:
738 <<
"legendE [label=<<font color=\"black\">O:<b><i> External Data Source</i></b></font>>, shape=plaintext];";
740 case ObjectKind::kOutgoingExternal:
742 <<
"legendF [label=<<font color=\"black\">X:<b><i> External Data Sink</i></b></font>>, shape=plaintext];";
748 if (std::ranges::find(legend_entries, legendstr.str()) == legend_entries.end()) {
749 legend_entries.emplace_back(legendstr.str());
753 std::ranges::sort(legend_entries);
766 auto legend_tokens = legend_entries | std::views::transform([](
const std::string& line) {
767 return line.substr(0, line.find(
' '));
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());
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();
782 std::string legend_code{};
783 legend_code +=
"\n\n\n";
785 for (
const auto& l : legend_entries) {
786 legend_code += l +
"\n";
789 legend_code +=
"\n\n\n";
791 for (
const auto& l : legend_ordering_code) {
792 legend_code += l +
"\n";
795 dotfile_slurped.insert(last_brace_loc, legend_code);
802 const std::string unlabeled_edge =
"label=\"\"";
803 const std::string edge_modifier =
", style=\"dotted\", arrowhead=\"none\"";
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();
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;
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();
830 std::ofstream outputfile;
831 outputfile.open(outputfilename);
833 if (outputfile.is_open()) {
834 outputfile << dotfile_slurped.c_str();
836 std::stringstream errmsg;
837 errmsg <<
"Unable to open requested file \"" << outputfilename <<
"\" for writing";
838 throw daqconf::GeneralGraphToolError(
ERS_HERE, errmsg.str());