58 { ObjectKind::kSegment, {
"Segment",
"Application" } },
59 { ObjectKind::kApplication, {
"Application",
"Module" } },
60 { ObjectKind::kModule, {
"Module" } } }
61 , m_root_object_kind{ ObjectKind::kUndefined }
62 , m_session{
nullptr }
63 , m_session_name{ sessionname }
70 }
catch (dunedaq::conffwk::Generic& exc) {
71 TLOG() <<
"Failed to load OKS database: " << exc <<
"\n";
76 std::vector<ConfigObject> session_objects{};
78 m_confdb->get(
"Session", session_objects);
80 if (m_session_name ==
"") {
81 if (session_objects.size() == 1) {
82 m_session_name = session_objects[0].UID();
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";
88 throw daqconf::GeneralGraphToolError(
ERS_HERE, errmsg.str());
92 std::ranges::find_if(session_objects, [&](
const ConfigObject& obj) {
return obj.UID() == m_session_name; });
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());
120 std::stringstream errmsg;
121 errmsg <<
"Unable to get session with UID \"" << m_session_name <<
"\"";
122 throw daqconf::GeneralGraphToolError(
ERS_HERE, errmsg.str());
125 std::vector<ConfigObject> every_object_deriving_from_class{};
126 std::vector<ConfigObject> objects_of_class{};
130 auto classnames = m_confdb->superclasses() | std::views::keys |
131 std::views::transform([](
const auto& ptr_to_class_name) {
return *ptr_to_class_name; });
133 for (
const auto&
classname : classnames) {
135 every_object_deriving_from_class.clear();
136 objects_of_class.clear();
138 m_confdb->get(
classname, every_object_deriving_from_class);
140 std::ranges::copy_if(every_object_deriving_from_class,
141 std::back_inserter(objects_of_class),
144 std::ranges::copy(objects_of_class, std::back_inserter(m_all_objects));
146 if (
classname.find(
"Application") != std::string::npos) {
147 for (
const auto& appobj : objects_of_class) {
155 if (res && res->is_disabled(*m_session)) {
156 m_ignored_application_uids.push_back(appobj.UID());
157 TLOG() <<
"Skipping disabled application " << appobj.UID() <<
"@" << daqapp->class_name();
161 TLOG(TLVL_DEBUG) <<
"Skipping non-SmartDaqApplication " << appobj.UID() <<
"@" << appobj.class_name();
162 m_ignored_application_uids.push_back(appobj.UID());
244 std::vector<std::string> incoming_matched;
245 std::vector<std::string> outgoing_matched;
249 std::regex incoming_pattern(incoming.first);
253 std::regex outgoing_pattern(outgoing.first);
257 if (incoming.first == outgoing.first) {
259 }
else if (incoming.first.find(
".*") != std::string::npos) {
260 if (std::regex_match(outgoing.first, incoming_pattern)) {
263 }
else if (outgoing.first.find(
".*") != std::string::npos) {
264 if (std::regex_match(incoming.first, outgoing_pattern)) {
271 bool low_level_plot =
274 for (
auto& receiver : incoming.second) {
275 for (
auto& sender : outgoing.second) {
287 }
else if (low_level_plot && incoming.first.find(
"NetworkConnection") != std::string::npos) {
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);
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);
308 auto res = std::ranges::find(
m_objects_for_graph.at(sender).receiving_object_infos, receiving_info);
318 auto incoming_unmatched =
320 return std::ranges::find(incoming_matched, connection) == incoming_matched.end();
325 for (
auto& incoming_conn : incoming_unmatched) {
328 const std::string incoming_vertex_name = incoming_conn;
337 if (std::ranges::find(included_classes,
"Module") != included_classes.end()) {
341 }
else if (std::ranges::find(included_classes,
"Application") != included_classes.end()) {
351 auto outgoing_unmatched =
353 return std::ranges::find(outgoing_matched, connection) == outgoing_matched.end();
356 for (
auto& outgoing_conn : outgoing_unmatched) {
359 const std::string outgoing_vertex_name = outgoing_conn;
368 if (std::ranges::find(included_classes,
"Module") != included_classes.end()) {
371 .receiving_object_infos.push_back({ outgoing_conn, outgoing_vertex_name });
373 }
else if (std::ranges::find(included_classes,
"Application") != included_classes.end()) {
376 .receiving_object_infos.push_back({ outgoing_conn, outgoing_vertex_name });
416 }
catch (dunedaq::conffwk::Generic& exc) {
417 TLOG() <<
"Failed to load OKS database: " << exc <<
"\n";
426 auto helper = std::make_shared<dunedaq::appmodel::ConfigurationHelper>(local_session);
427 daqapp->generate_modules(helper);
428 auto modules = daqapp->get_modules();
430 std::vector<std::string> allowed_conns{};
433 allowed_conns = {
"NetworkConnection" };
435 allowed_conns = {
"NetworkConnection",
"Queue",
"QueueWithSourceId",
"DataMoveCallbackConf" };
438 for (
const auto& module : modules) {
440 for (
auto in : module->get_inputs()) {
446 std::string key = in->config_object().UID() +
"@" + in->config_object().class_name();
448 if (in->config_object().class_name() ==
"NetworkConnection") {
450 key +=
"@" + innc->get_connection_type();
453 if (std::ranges::find(allowed_conns, in->config_object().class_name()) != allowed_conns.end()) {
459 for (
auto out : module->get_outputs()) {
461 std::string key = out->config_object().UID() +
"@" + out->config_object().class_name();
463 if (out->config_object().class_name() ==
"NetworkConnection") {
465 key +=
"@" + outnc->get_connection_type();
468 if (std::ranges::find(allowed_conns, out->config_object().class_name()) != allowed_conns.end()) {
475 auto datareader =
module->cast<dunedaq::appmodel::DataReaderModule>();
476 auto datahandler =
module->cast<dunedaq::appmodel::DataHandlerModule>();
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()) {
487 if (datahandler !=
nullptr) {
488 auto in = datahandler->get_raw_data_callback();
490 const std::string key = in->config_object().UID() +
"@" + in->config_object().class_name();
492 if (std::ranges::find(allowed_conns, in->config_object().class_name()) != allowed_conns.end()) {
517 if (root_obj_uid ==
"") {
523 auto class_names_view =
525 std::views::transform([](
const ConfigObject& obj) {
return obj.class_name(); });
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());
533 const std::string& root_obj_class_name = *class_names_view.begin();
546 auto& obj = enhanced_object.config_object;
547 enhanced_object.vertex_in_graph = boost::add_vertex(
VertexLabel(obj.UID(), obj.class_name()),
m_graph);
552 for (
auto& child_obj_name : parent_obj.child_object_names) {
553 boost::add_edge(parent_obj.vertex_in_graph,
561 for (
auto& receiver_info : possible_sender_object.receiving_object_infos) {
580 boost::add_edge(possible_sender_object.vertex_in_graph,
582 { receiver_info.connection_name },
625 std::stringstream outputstream;
627 boost::write_graphviz(outputstream,
640 const std::string shape;
641 const std::string color;
644 const std::unordered_map<ObjectKind, VertexStyle> vertex_styles{ {
ObjectKind::kSession, {
"octagon",
"black" } },
649 std::string dotfile_slurped = outputstream.str();
650 std::vector<std::string> legend_entries{
651 "legendGA [label=<<font color=\"black\"><b><i>⟶ Network Connection</i></b></font>>, shape=plaintext];",
652 "legendGB [label=<<font color=\"blue\"><b><i>⟶ Pub/Sub Network</i></b></font>>, shape=plaintext];"
654 std::vector<std::string> internal_legend_entries{
655 "legendGC [label=<<font color=\"green\"><b><i>⟶ Data Move Callback</i></b></font>>, shape=plaintext];",
656 "legendGD [label=<<font color=\"red\"><b><i>⟶ Queue</i></b></font>>, shape=plaintext];",
657 "legendGE [label=<<font color=\"orange\"><b><i>⟶ Queue w/ Source ID</i></b></font>>, shape=plaintext];"
659 bool internal_legend_added =
false;
660 std::vector<std::string> legend_ordering_code{};
664 std::stringstream vertexstr{};
665 std::stringstream legendstr{};
666 std::stringstream labelstringstr{};
667 size_t insertion_location{ 0 };
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;
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());
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 <<
"];";
703 add_legend_entry(
'A',
"session");
707 add_legend_entry(
'B',
"segment");
711 add_legend_entry(
'C',
"application");
715 add_legend_entry(
'D',
"DAQModule");
716 if (!internal_legend_added) {
717 legend_entries.insert(legend_entries.end(),
718 internal_legend_entries.begin(),
719 internal_legend_entries.end());
720 internal_legend_added =
true;
725 <<
"legendE [label=<<font color=\"black\">O:<b><i> External Data Source</i></b></font>>, shape=plaintext];";
729 <<
"legendF [label=<<font color=\"black\">X:<b><i> External Data Sink</i></b></font>>, shape=plaintext];";
735 if (std::ranges::find(legend_entries, legendstr.str()) == legend_entries.end()) {
736 legend_entries.emplace_back(legendstr.str());
740 std::ranges::sort(legend_entries);
753 auto legend_tokens = legend_entries | std::views::transform([](
const std::string& line) {
754 return line.substr(0, line.find(
' '));
757 auto it = legend_tokens.begin();
758 for (
auto next_it = std::next(it); next_it != legend_tokens.end(); ++it, ++next_it) {
759 std::stringstream astr{};
760 astr <<
" " << *it <<
" -> " << *next_it <<
" [style=invis];";
761 legend_ordering_code.push_back(astr.str());
764 constexpr int chars_to_last_brace = 2;
765 auto last_brace_iter = dotfile_slurped.end() - chars_to_last_brace;
766 assert(*last_brace_iter ==
'}');
767 size_t last_brace_loc = last_brace_iter - dotfile_slurped.begin();
769 std::string legend_code{};
770 legend_code +=
"\n\n\n";
772 for (
const auto& l : legend_entries) {
773 legend_code += l +
"\n";
776 legend_code +=
"\n\n\n";
778 for (
const auto& l : legend_ordering_code) {
779 legend_code += l +
"\n";
782 dotfile_slurped.insert(last_brace_loc, legend_code);
789 const std::string unlabeled_edge =
"label=\"\"";
790 const std::string edge_modifier =
", style=\"dotted\", arrowhead=\"none\"";
793 while ((pos = dotfile_slurped.find(unlabeled_edge, pos)) != std::string::npos) {
794 dotfile_slurped.replace(pos, unlabeled_edge.length(), unlabeled_edge + edge_modifier);
795 pos += (unlabeled_edge + edge_modifier).length();
799 std::vector<std::pair<std::string, std::string>> connection_colors = { {
"@NetworkConnection@kSendRecv\"",
"\", color=black" },
800 {
"@NetworkConnection@kPubSub\"",
"\", color=blue" },
801 {
"@QueueWithSourceId\"",
"\", color=orange" },
802 {
"@Queue\"",
"\", color=red" },
803 {
"@DataMoveCallbackConf\"",
804 "\", color=green" } };
805 for (
auto& color_pair : connection_colors) {
806 auto conn_type = color_pair.first;
807 auto color_info = color_pair.second;
809 while ((pos = dotfile_slurped.find(conn_type, pos)) != std::string::npos) {
810 dotfile_slurped.replace(pos, conn_type.length(), color_info);
811 pos += color_info.length();
817 std::ofstream outputfile;
818 outputfile.open(outputfilename);
820 if (outputfile.is_open()) {
821 outputfile << dotfile_slurped.c_str();
823 std::stringstream errmsg;
824 errmsg <<
"Unable to open requested file \"" << outputfilename <<
"\" for writing";
825 throw daqconf::GeneralGraphToolError(
ERS_HERE, errmsg.str());