From 4b588759b204ef5af693581958b4677157cf6ea0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 13:52:05 +0000 Subject: [PATCH 01/29] Initial plan From ca26b639c41a802be22a220a948514155d4ba83a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 14:03:42 +0000 Subject: [PATCH 02/29] Implement janadot plugin with graph splitting functionality Co-authored-by: wdconinc <4656391+wdconinc@users.noreply.github.com> --- docs/howto/visualize_callgraph.md | 25 + src/utilities/CMakeLists.txt | 1 + src/utilities/janadot/CMakeLists.txt | 13 + .../janadot/JEventProcessorJANADOT.cc | 549 ++++++++++++++++++ .../janadot/JEventProcessorJANADOT.h | 121 ++++ src/utilities/janadot/README.md | 57 ++ src/utilities/janadot/janadot.cc | 18 + 7 files changed, 784 insertions(+) create mode 100644 src/utilities/janadot/CMakeLists.txt create mode 100644 src/utilities/janadot/JEventProcessorJANADOT.cc create mode 100644 src/utilities/janadot/JEventProcessorJANADOT.h create mode 100644 src/utilities/janadot/README.md create mode 100644 src/utilities/janadot/janadot.cc diff --git a/docs/howto/visualize_callgraph.md b/docs/howto/visualize_callgraph.md index 7ff36da775..b76e8e5f45 100644 --- a/docs/howto/visualize_callgraph.md +++ b/docs/howto/visualize_callgraph.md @@ -9,6 +9,10 @@ sources in the event. It also times how long it takes each factory to run, integrating it over all calls so that one can see the relative time spent in each factory. +The `janadot` plugin in EICrecon includes enhanced functionality to automatically +split large graphs into multiple smaller graphs for better processing by graphviz +and improved readability. + *Note that this requires JANA2 v2.0.8 or later and EICrecon v0.3.6 or later.* @@ -43,6 +47,27 @@ you would run: dot -Tpng jana.dot -o jana.png ~~~ +### Graph Splitting for Large Call Graphs +When processing complex reconstructions with many algorithms, the resulting call graph can become too large for graphviz to handle efficiently. The janadot plugin automatically detects large graphs and splits them into multiple smaller graphs. + +To control the splitting behavior, you can use these parameters: + +~~~bash +# Enable/disable splitting (enabled by default) +eicrecon -Pplugins=janadot -Pjanadot:enable_splitting=false sim_file.edm4hep.root + +# Control splitting thresholds +eicrecon -Pplugins=janadot \ + -Pjanadot:max_nodes_per_graph=30 \ + -Pjanadot:max_edges_per_graph=60 \ + sim_file.edm4hep.root + +# Change splitting method (size, components, or type) +eicrecon -Pplugins=janadot -Pjanadot:split_criteria=components sim_file.edm4hep.root +~~~ + +When graphs are split, multiple files are created (`jana_part001.dot`, `jana_part002.dot`, etc.) along with an index file (`jana_index.txt`) that explains how to process them. + ### Running for a single detector By default `eicrecon` activates the full reconstruction. This will result in a very busy callgraph that can be hard to read if you diff --git a/src/utilities/CMakeLists.txt b/src/utilities/CMakeLists.txt index 646f4613dd..7af312d2aa 100644 --- a/src/utilities/CMakeLists.txt +++ b/src/utilities/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(dump_flags) add_subdirectory(eicrecon) +add_subdirectory(janadot) add_subdirectory(janatop) diff --git a/src/utilities/janadot/CMakeLists.txt b/src/utilities/janadot/CMakeLists.txt new file mode 100644 index 0000000000..9451d381aa --- /dev/null +++ b/src/utilities/janadot/CMakeLists.txt @@ -0,0 +1,13 @@ +# Automatically set plugin name the same as the directory name Don't forget +# string(REPLACE " " "_" PLUGIN_NAME ${PLUGIN_NAME}) if this dir has spaces in +# its name +get_filename_component(PLUGIN_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) + +# Function creates ${PLUGIN_NAME}_plugin and ${PLUGIN_NAME}_library targets +# Setting default includes, libraries and installation paths +plugin_add(${PLUGIN_NAME}) + +# The macro grabs sources as *.cc *.cpp *.c and headers as *.h *.hh *.hpp Then +# correctly sets sources for ${_name}_plugin and ${_name}_library targets Adds +# headers to the correct installation directory +plugin_glob_all(${PLUGIN_NAME}) \ No newline at end of file diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc new file mode 100644 index 0000000000..d73dd3a6ec --- /dev/null +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright (C) 2024, EICrecon contributors + +#include "JEventProcessorJANADOT.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +void JEventProcessorJANADOT::Init() { + // Get parameter manager + auto params = GetApplication()->GetJParameterManager(); + + // Set default parameter values and register them + output_filename = "jana.dot"; + params->SetDefaultParameter("janadot:output_file", output_filename, + "Output DOT filename"); + + enable_splitting = true; + params->SetDefaultParameter("janadot:enable_splitting", enable_splitting, + "Enable splitting large graphs into multiple files"); + + max_nodes_per_graph = 50; + params->SetDefaultParameter("janadot:max_nodes_per_graph", max_nodes_per_graph, + "Maximum number of nodes per graph when splitting"); + + max_edges_per_graph = 100; + params->SetDefaultParameter("janadot:max_edges_per_graph", max_edges_per_graph, + "Maximum number of edges per graph when splitting"); + + split_criteria = "size"; + params->SetDefaultParameter("janadot:split_criteria", split_criteria, + "Criteria for splitting graphs: size, components, type"); +} + +void JEventProcessorJANADOT::Process(const std::shared_ptr& event) { + // Get the call stack for this event and add the results to our stats + auto stack = event->GetJCallGraphRecorder()->GetCallGraph(); + + // Lock mutex in case we are running with multiple threads + std::lock_guard lck(mutex); + + // Loop over the call stack elements and add in the values + for (unsigned int i = 0; i < stack.size(); i++) { + + // Keep track of total time each factory spent waiting and being waited on + std::string nametag1 = MakeNametag(stack[i].caller_name, stack[i].caller_tag); + std::string nametag2 = MakeNametag(stack[i].callee_name, stack[i].callee_tag); + + FactoryCallStats& fcallstats1 = factory_stats[nametag1]; + FactoryCallStats& fcallstats2 = factory_stats[nametag2]; + + // Determine node types + fcallstats1.type = GetNodeType(stack[i].caller_name, stack[i].caller_tag); + fcallstats2.type = GetNodeType(stack[i].callee_name, stack[i].callee_tag); + + auto delta_t_ms = std::chrono::duration_cast(stack[i].end_time - + stack[i].start_time) + .count(); + fcallstats1.time_waiting += delta_t_ms; + fcallstats2.time_waited_on += delta_t_ms; + + // Get pointer to CallStats object representing this calling pair + CallLink link; + link.caller_name = stack[i].caller_name; + link.caller_tag = stack[i].caller_tag; + link.callee_name = stack[i].callee_name; + link.callee_tag = stack[i].callee_tag; + CallStats& stats = + call_links[link]; // get pointer to stats object or create if it doesn't exist + + switch (stack[i].data_source) { + case JCallGraphRecorder::DATA_NOT_AVAILABLE: + stats.Ndata_not_available++; + stats.data_not_available_ms += delta_t_ms; + break; + case JCallGraphRecorder::DATA_FROM_CACHE: + fcallstats2.Nfrom_cache++; + stats.Nfrom_cache++; + stats.from_cache_ms += delta_t_ms; + break; + case JCallGraphRecorder::DATA_FROM_SOURCE: + fcallstats2.Nfrom_source++; + stats.Nfrom_source++; + stats.from_source_ms += delta_t_ms; + break; + case JCallGraphRecorder::DATA_FROM_FACTORY: + fcallstats2.Nfrom_factory++; + stats.Nfrom_factory++; + stats.from_factory_ms += delta_t_ms; + break; + } + } +} + +void JEventProcessorJANADOT::Finish() { + WriteDotFile(); +} + +void JEventProcessorJANADOT::WriteDotFile() { + int total_nodes, total_edges; + AnalyzeGraph(total_nodes, total_edges); + + std::cout << "Graph analysis: " << total_nodes << " nodes, " << total_edges << " edges" << std::endl; + + if (enable_splitting && ShouldSplitGraph(total_nodes, total_edges)) { + std::cout << "Graph is large, splitting into multiple files..." << std::endl; + WriteSplitDotFiles(); + } else { + WriteSingleDotFile(output_filename); + std::cout << std::endl; + std::cout << "Factory calling information written to \"" << output_filename << "\". To create a graphic" << std::endl; + std::cout << "from this, use the dot program. For example, to make a PDF file do the following:" << std::endl; + std::cout << std::endl; + std::cout << " dot -Tpdf " << output_filename << " -o jana.pdf" << std::endl; + std::cout << std::endl; + std::cout << "This should give you a file named \"jana.pdf\"." << std::endl; + std::cout << std::endl; + } +} + +void JEventProcessorJANADOT::WriteSingleDotFile(const std::string& filename) { + std::cout << "Opening output file \"" << filename << "\"" << std::endl; + + std::ofstream ofs(filename); + if (!ofs.is_open()) { + std::cerr << "Error: Unable to open file " << filename << " for writing!" << std::endl; + return; + } + + // Calculate total time for percentages + double total_ms = 0.0; + for (auto& [link, stats] : call_links) { + std::string caller = MakeNametag(link.caller_name, link.caller_tag); + // Only count top-level callers (those not called by others) + bool is_top_level = true; + for (auto& [other_link, other_stats] : call_links) { + std::string other_callee = MakeNametag(other_link.callee_name, other_link.callee_tag); + if (other_callee == caller) { + is_top_level = false; + break; + } + } + if (is_top_level) { + total_ms += stats.from_factory_ms + stats.from_source_ms + + stats.from_cache_ms + stats.data_not_available_ms; + } + } + if (total_ms == 0.0) total_ms = 1.0; + + // Write DOT file header + ofs << "digraph G {" << std::endl; + ofs << " rankdir=TB;" << std::endl; + ofs << " node [fontname=\"Arial\", fontsize=10];" << std::endl; + ofs << " edge [fontname=\"Arial\", fontsize=8];" << std::endl; + ofs << std::endl; + + // Write nodes + for (auto& [nametag, fstats] : factory_stats) { + double time_in_factory = fstats.time_waited_on - fstats.time_waiting; + double percent = 100.0 * time_in_factory / total_ms; + + std::string color = GetNodeColor(fstats.type); + std::string shape = GetNodeShape(fstats.type); + + ofs << " \"" << nametag << "\" ["; + ofs << "fillcolor=" << color << ", "; + ofs << "style=filled, "; + ofs << "shape=" << shape << ", "; + ofs << "label=\"" << nametag << "\\n"; + ofs << MakeTimeString(time_in_factory) << " (" << std::fixed << std::setprecision(1) << percent << "%)\""; + ofs << "];" << std::endl; + } + + ofs << std::endl; + + // Write edges + for (auto& [link, stats] : call_links) { + std::string caller = MakeNametag(link.caller_name, link.caller_tag); + std::string callee = MakeNametag(link.callee_name, link.callee_tag); + + unsigned int total_calls = stats.Nfrom_cache + stats.Nfrom_source + + stats.Nfrom_factory + stats.Ndata_not_available; + double total_time = stats.from_cache_ms + stats.from_source_ms + + stats.from_factory_ms + stats.data_not_available_ms; + double percent = 100.0 * total_time / total_ms; + + ofs << " \"" << caller << "\" -> \"" << callee << "\" ["; + ofs << "label=\"" << total_calls << " calls\\n"; + ofs << MakeTimeString(total_time) << " (" << std::fixed << std::setprecision(1) << percent << "%)\""; + ofs << "];" << std::endl; + } + + ofs << "}" << std::endl; + ofs.close(); +} + +void JEventProcessorJANADOT::WriteSplitDotFiles() { + std::vector> node_groups; + + if (split_criteria == "components") { + node_groups = SplitGraphByConnectedComponents(); + } else if (split_criteria == "type") { + node_groups = SplitGraphByType(); + } else { // default to "size" + node_groups = SplitGraphBySize(); + } + + std::cout << "Splitting graph into " << node_groups.size() << " subgraphs" << std::endl; + + for (size_t i = 0; i < node_groups.size(); i++) { + // Create filename for this subgraph + std::string base_filename = output_filename; + size_t dot_pos = base_filename.find_last_of('.'); + if (dot_pos != std::string::npos) { + base_filename = base_filename.substr(0, dot_pos); + } + + std::stringstream ss; + ss << base_filename << "_part" << std::setfill('0') << std::setw(3) << (i + 1) << ".dot"; + std::string filename = ss.str(); + + WriteSplitDotFile(filename, node_groups[i]); + } + + // Write an index file explaining the split + WriteIndexFile(node_groups.size()); +} + +void JEventProcessorJANADOT::WriteSplitDotFile(const std::string& filename, const std::set& nodes) { + std::ofstream ofs(filename); + if (!ofs.is_open()) { + std::cerr << "Error: Unable to open file " << filename << " for writing!" << std::endl; + return; + } + + // Calculate total time for this subgraph + double total_ms = 0.0; + for (auto& [link, stats] : call_links) { + std::string caller = MakeNametag(link.caller_name, link.caller_tag); + if (nodes.find(caller) != nodes.end()) { + total_ms += stats.from_factory_ms + stats.from_source_ms + + stats.from_cache_ms + stats.data_not_available_ms; + } + } + if (total_ms == 0.0) total_ms = 1.0; + + // Write DOT file header + ofs << "digraph G {" << std::endl; + ofs << " rankdir=TB;" << std::endl; + ofs << " node [fontname=\"Arial\", fontsize=10];" << std::endl; + ofs << " edge [fontname=\"Arial\", fontsize=8];" << std::endl; + ofs << " label=\"EICrecon Call Graph (Part " << filename.substr(filename.find("part")) << ")\";" << std::endl; + ofs << " labelloc=\"t\";" << std::endl; + ofs << std::endl; + + // Write nodes (only those in this group) + for (const std::string& nametag : nodes) { + auto fstats_it = factory_stats.find(nametag); + if (fstats_it == factory_stats.end()) continue; + + const FactoryCallStats& fstats = fstats_it->second; + double time_in_factory = fstats.time_waited_on - fstats.time_waiting; + double percent = 100.0 * time_in_factory / total_ms; + + std::string color = GetNodeColor(fstats.type); + std::string shape = GetNodeShape(fstats.type); + + ofs << " \"" << nametag << "\" ["; + ofs << "fillcolor=" << color << ", "; + ofs << "style=filled, "; + ofs << "shape=" << shape << ", "; + ofs << "label=\"" << nametag << "\\n"; + ofs << MakeTimeString(time_in_factory) << " (" << std::fixed << std::setprecision(1) << percent << "%)\""; + ofs << "];" << std::endl; + } + + ofs << std::endl; + + // Write edges (only those within this group) + for (auto& [link, stats] : call_links) { + std::string caller = MakeNametag(link.caller_name, link.caller_tag); + std::string callee = MakeNametag(link.callee_name, link.callee_tag); + + // Only include edges where both nodes are in this group + if (nodes.find(caller) == nodes.end() || nodes.find(callee) == nodes.end()) { + continue; + } + + unsigned int total_calls = stats.Nfrom_cache + stats.Nfrom_source + + stats.Nfrom_factory + stats.Ndata_not_available; + double total_time = stats.from_cache_ms + stats.from_source_ms + + stats.from_factory_ms + stats.data_not_available_ms; + double percent = 100.0 * total_time / total_ms; + + ofs << " \"" << caller << "\" -> \"" << callee << "\" ["; + ofs << "label=\"" << total_calls << " calls\\n"; + ofs << MakeTimeString(total_time) << " (" << std::fixed << std::setprecision(1) << percent << "%)\""; + ofs << "];" << std::endl; + } + + ofs << "}" << std::endl; + ofs.close(); +} + +void JEventProcessorJANADOT::WriteIndexFile(int num_parts) { + std::string base_filename = output_filename; + size_t dot_pos = base_filename.find_last_of('.'); + if (dot_pos != std::string::npos) { + base_filename = base_filename.substr(0, dot_pos); + } + + std::string index_filename = base_filename + "_index.txt"; + std::ofstream ofs(index_filename); + if (!ofs.is_open()) { + std::cerr << "Error: Unable to open index file " << index_filename << " for writing!" << std::endl; + return; + } + + ofs << "EICrecon Call Graph Split Information" << std::endl; + ofs << "=====================================" << std::endl; + ofs << std::endl; + ofs << "The call graph was too large for efficient processing by graphviz," << std::endl; + ofs << "so it has been split into " << num_parts << " separate DOT files:" << std::endl; + ofs << std::endl; + + for (int i = 1; i <= num_parts; i++) { + std::stringstream ss; + ss << base_filename << "_part" << std::setfill('0') << std::setw(3) << i << ".dot"; + ofs << " " << ss.str() << std::endl; + } + + ofs << std::endl; + ofs << "To generate PDF files from each part:" << std::endl; + ofs << std::endl; + + for (int i = 1; i <= num_parts; i++) { + std::stringstream ss_dot, ss_pdf; + ss_dot << base_filename << "_part" << std::setfill('0') << std::setw(3) << i << ".dot"; + ss_pdf << base_filename << "_part" << std::setfill('0') << std::setw(3) << i << ".pdf"; + ofs << " dot -Tpdf " << ss_dot.str() << " -o " << ss_pdf.str() << std::endl; + } + + ofs << std::endl; + ofs << "Configuration used:" << std::endl; + ofs << " Split criteria: " << split_criteria << std::endl; + ofs << " Max nodes per graph: " << max_nodes_per_graph << std::endl; + ofs << " Max edges per graph: " << max_edges_per_graph << std::endl; + + ofs.close(); + + std::cout << std::endl; + std::cout << "Factory calling information written to " << num_parts << " files." << std::endl; + std::cout << "See \"" << index_filename << "\" for details on processing the files." << std::endl; + std::cout << std::endl; +} + +std::string JEventProcessorJANADOT::MakeTimeString(double time_in_ms) { + double order = log10(std::abs(time_in_ms) + 1e-9); // avoid log(0) + std::stringstream ss; + ss << std::fixed << std::setprecision(2); + if (order < 0) { + ss << time_in_ms * 1000.0 << " us"; + } else if (order <= 2.0) { + ss << time_in_ms << " ms"; + } else { + ss << time_in_ms / 1000.0 << " s"; + } + return ss.str(); +} + +std::string JEventProcessorJANADOT::MakeNametag(const std::string& name, const std::string& tag) { + std::string nametag = name; + if (tag.size() > 0) + nametag += ":" + tag; + return nametag; +} + +JEventProcessorJANADOT::node_type JEventProcessorJANADOT::GetNodeType(const std::string& name, const std::string& tag) { + // Simple heuristics to determine node type based on name patterns + if (name.find("Processor") != std::string::npos || name.find("PROCESSOR") != std::string::npos) { + return kProcessor; + } else if (name.find("Source") != std::string::npos || name.find("SOURCE") != std::string::npos) { + return kSource; + } else if (name.find("Cache") != std::string::npos || name.find("CACHE") != std::string::npos) { + return kCache; + } else if (!tag.empty() || name.find("Factory") != std::string::npos || name.find("edm4") != std::string::npos) { + return kFactory; + } + return kDefault; +} + +std::string JEventProcessorJANADOT::GetNodeColor(node_type type) { + switch (type) { + case kProcessor: return "lightgreen"; + case kFactory: return "lightblue"; + case kCache: return "yellow"; + case kSource: return "lightcoral"; + default: return "white"; + } +} + +std::string JEventProcessorJANADOT::GetNodeShape(node_type type) { + switch (type) { + case kProcessor: return "ellipse"; + case kFactory: return "box"; + case kCache: return "diamond"; + case kSource: return "trapezium"; + default: return "ellipse"; + } +} + +void JEventProcessorJANADOT::AnalyzeGraph(int& total_nodes, int& total_edges) { + total_nodes = factory_stats.size(); + total_edges = call_links.size(); +} + +bool JEventProcessorJANADOT::ShouldSplitGraph(int total_nodes, int total_edges) { + return (total_nodes > max_nodes_per_graph) || (total_edges > max_edges_per_graph); +} + +std::vector> JEventProcessorJANADOT::SplitGraphBySize() { + std::vector> groups; + std::set current_group; + + int current_nodes = 0; + int current_edges = 0; + + // Simple greedy algorithm: add nodes to current group until limits are reached + for (auto& [nametag, fstats] : factory_stats) { + // Count edges involving this node + int node_edges = 0; + for (auto& [link, stats] : call_links) { + std::string caller = MakeNametag(link.caller_name, link.caller_tag); + std::string callee = MakeNametag(link.callee_name, link.callee_tag); + if (caller == nametag || callee == nametag) { + // Only count edge if both nodes are in current group (or this is the first node) + if (current_group.empty() || + current_group.find(caller) != current_group.end() || + current_group.find(callee) != current_group.end()) { + node_edges++; + } + } + } + + // Check if adding this node would exceed limits + if (current_nodes > 0 && + (current_nodes + 1 > max_nodes_per_graph || current_edges + node_edges > max_edges_per_graph)) { + groups.push_back(current_group); + current_group.clear(); + current_nodes = 0; + current_edges = 0; + } + + current_group.insert(nametag); + current_nodes++; + current_edges += node_edges; + } + + if (!current_group.empty()) { + groups.push_back(current_group); + } + + // If we only have one group, return it as-is (even if it's large) + if (groups.empty()) { + std::set all_nodes; + for (auto& [nametag, fstats] : factory_stats) { + all_nodes.insert(nametag); + } + groups.push_back(all_nodes); + } + + return groups; +} + +std::vector> JEventProcessorJANADOT::SplitGraphByConnectedComponents() { + // Implementation of connected components finding using Union-Find + std::map parent; + + // Initialize each node as its own parent + for (auto& [nametag, fstats] : factory_stats) { + parent[nametag] = nametag; + } + + // Union-Find helper functions + std::function find = [&](const std::string& x) -> std::string { + if (parent[x] != x) { + parent[x] = find(parent[x]); + } + return parent[x]; + }; + + auto unite = [&](const std::string& x, const std::string& y) { + std::string px = find(x); + std::string py = find(y); + if (px != py) { + parent[px] = py; + } + }; + + // Connect nodes that have edges between them + for (auto& [link, stats] : call_links) { + std::string caller = MakeNametag(link.caller_name, link.caller_tag); + std::string callee = MakeNametag(link.callee_name, link.callee_tag); + unite(caller, callee); + } + + // Group nodes by their root parent + std::map> components; + for (auto& [nametag, fstats] : factory_stats) { + components[find(nametag)].insert(nametag); + } + + // Convert to vector of sets + std::vector> groups; + for (auto& [root, component] : components) { + groups.push_back(component); + } + + return groups; +} + +std::vector> JEventProcessorJANADOT::SplitGraphByType() { + std::map> type_groups; + + // Group nodes by their type + for (auto& [nametag, fstats] : factory_stats) { + type_groups[fstats.type].insert(nametag); + } + + // Convert to vector of sets + std::vector> groups; + for (auto& [type, group] : type_groups) { + if (!group.empty()) { + groups.push_back(group); + } + } + + return groups; +} \ No newline at end of file diff --git a/src/utilities/janadot/JEventProcessorJANADOT.h b/src/utilities/janadot/JEventProcessorJANADOT.h new file mode 100644 index 0000000000..8b5113d856 --- /dev/null +++ b/src/utilities/janadot/JEventProcessorJANADOT.h @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright (C) 2024, EICrecon contributors + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +class JEventProcessorJANADOT : public JEventProcessor { +private: + enum node_type { kDefault, kProcessor, kFactory, kCache, kSource }; + + class CallLink { + public: + std::string caller_name; + std::string caller_tag; + std::string callee_name; + std::string callee_tag; + + bool operator<(const CallLink& link) const { + if (this->caller_name != link.caller_name) + return this->caller_name < link.caller_name; + if (this->callee_name != link.callee_name) + return this->callee_name < link.callee_name; + if (this->caller_tag != link.caller_tag) + return this->caller_tag < link.caller_tag; + return this->callee_tag < link.callee_tag; + } + }; + + class CallStats { + public: + CallStats(void) { + from_cache_ms = 0; + from_source_ms = 0; + from_factory_ms = 0; + data_not_available_ms = 0; + Nfrom_cache = 0; + Nfrom_source = 0; + Nfrom_factory = 0; + Ndata_not_available = 0; + } + double from_cache_ms; + double from_source_ms; + double from_factory_ms; + double data_not_available_ms; + unsigned int Nfrom_cache; + unsigned int Nfrom_source; + unsigned int Nfrom_factory; + unsigned int Ndata_not_available; + }; + + class FactoryCallStats { + public: + FactoryCallStats(void) { + type = kDefault; + time_waited_on = 0.0; + time_waiting = 0.0; + Nfrom_factory = 0; + Nfrom_source = 0; + Nfrom_cache = 0; + } + node_type type; + double time_waited_on; // time other factories spent waiting on this factory + double time_waiting; // time this factory spent waiting on other factories + unsigned int Nfrom_factory; + unsigned int Nfrom_source; + unsigned int Nfrom_cache; + }; + +public: + JEventProcessorJANADOT() : JEventProcessor() { + SetTypeName("JEventProcessorJANADOT"); + }; + + void Init() override; + void BeginRun(const std::shared_ptr& /* event */) override {}; + void Process(const std::shared_ptr& event) override; + void EndRun() override {}; + void Finish() override; + +private: + std::mutex mutex; + + std::map call_links; + std::map factory_stats; + + // Configuration parameters + std::string output_filename; + bool enable_splitting; + int max_nodes_per_graph; + int max_edges_per_graph; + std::string split_criteria; + + // Helper methods + std::string MakeTimeString(double time_in_ms); + std::string MakeNametag(const std::string& name, const std::string& tag); + node_type GetNodeType(const std::string& name, const std::string& tag); + std::string GetNodeColor(node_type type); + std::string GetNodeShape(node_type type); + + // DOT file generation methods + void WriteDotFile(); + void WriteSingleDotFile(const std::string& filename); + void WriteSplitDotFiles(); + void WriteSplitDotFile(const std::string& filename, const std::set& nodes); + void WriteIndexFile(int num_parts); + std::vector> SplitGraphByConnectedComponents(); + std::vector> SplitGraphBySize(); + std::vector> SplitGraphByType(); + + // Graph analysis methods + void AnalyzeGraph(int& total_nodes, int& total_edges); + bool ShouldSplitGraph(int total_nodes, int total_edges); +}; \ No newline at end of file diff --git a/src/utilities/janadot/README.md b/src/utilities/janadot/README.md new file mode 100644 index 0000000000..42905c233f --- /dev/null +++ b/src/utilities/janadot/README.md @@ -0,0 +1,57 @@ +# JANADOT Plugin + +This plugin creates DOT (graphviz) files from JANA2 call graphs for visualization purposes. + +## Overview + +The janadot plugin records call stack information during event processing and generates DOT format files that can be processed by graphviz to create visual call graphs. It includes functionality to split large graphs into multiple smaller graphs for better processing and readability. + +## Usage + +To use the plugin, add it to your eicrecon command: + +```bash +eicrecon -Pplugins=janadot sim_file.edm4hep.root +``` + +## Configuration Parameters + +The plugin supports several configuration parameters: + +- `janadot:output_file` (default: "jana.dot") - Output DOT filename +- `janadot:enable_splitting` (default: true) - Enable splitting large graphs into multiple files +- `janadot:max_nodes_per_graph` (default: 50) - Maximum number of nodes per graph when splitting +- `janadot:max_edges_per_graph` (default: 100) - Maximum number of edges per graph when splitting +- `janadot:split_criteria` (default: "size") - Criteria for splitting graphs: size, components, type + +## Output Files + +When splitting is disabled or graphs are small enough, a single DOT file is generated. When splitting is enabled and graphs are large, multiple files are created: + +- `jana_part001.dot`, `jana_part002.dot`, etc. - Individual graph parts +- `jana_index.txt` - Index file explaining the split and how to process the files + +## Generating Graphics + +To convert DOT files to graphics: + +```bash +# For a single file +dot -Tpdf jana.dot -o jana.pdf + +# For split files (as shown in the index file) +dot -Tpdf jana_part001.dot -o jana_part001.pdf +dot -Tpdf jana_part002.dot -o jana_part002.pdf +# ... etc +``` + +## Graph Splitting Methods + +### Size-based Splitting +Nodes are grouped to keep within the specified limits of nodes and edges per graph. + +### Component-based Splitting +Uses connected components analysis to group nodes that are connected by call relationships. + +### Type-based Splitting +Groups nodes by their type (Processor, Factory, Source, etc.). \ No newline at end of file diff --git a/src/utilities/janadot/janadot.cc b/src/utilities/janadot/janadot.cc new file mode 100644 index 0000000000..cf56c59142 --- /dev/null +++ b/src/utilities/janadot/janadot.cc @@ -0,0 +1,18 @@ +// Copyright 2024, EICrecon contributors +// Subject to the terms in the LICENSE file found in the top-level directory. +// +// + +#include +#include +#include + +#include "JEventProcessorJANADOT.h" + +extern "C" { +void InitPlugin(JApplication* app) { + InitJANAPlugin(app); + app->Add(new JEventProcessorJANADOT()); + app->GetJParameterManager()->SetParameter("RECORD_CALL_STACK", true); +} +} \ No newline at end of file From 080b34963e7ae6734072df60b9383c2f68184a15 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Aug 2025 14:04:29 +0000 Subject: [PATCH 03/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/utilities/janadot/CMakeLists.txt | 2 +- .../janadot/JEventProcessorJANADOT.cc | 251 ++++++++++-------- .../janadot/JEventProcessorJANADOT.h | 18 +- src/utilities/janadot/README.md | 6 +- src/utilities/janadot/janadot.cc | 2 +- 5 files changed, 149 insertions(+), 130 deletions(-) diff --git a/src/utilities/janadot/CMakeLists.txt b/src/utilities/janadot/CMakeLists.txt index 9451d381aa..413cc447a1 100644 --- a/src/utilities/janadot/CMakeLists.txt +++ b/src/utilities/janadot/CMakeLists.txt @@ -10,4 +10,4 @@ plugin_add(${PLUGIN_NAME}) # The macro grabs sources as *.cc *.cpp *.c and headers as *.h *.hh *.hpp Then # correctly sets sources for ${_name}_plugin and ${_name}_library targets Adds # headers to the correct installation directory -plugin_glob_all(${PLUGIN_NAME}) \ No newline at end of file +plugin_glob_all(${PLUGIN_NAME}) diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index d73dd3a6ec..c5077cb483 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -19,27 +19,26 @@ void JEventProcessorJANADOT::Init() { // Get parameter manager auto params = GetApplication()->GetJParameterManager(); - + // Set default parameter values and register them output_filename = "jana.dot"; - params->SetDefaultParameter("janadot:output_file", output_filename, - "Output DOT filename"); - + params->SetDefaultParameter("janadot:output_file", output_filename, "Output DOT filename"); + enable_splitting = true; params->SetDefaultParameter("janadot:enable_splitting", enable_splitting, - "Enable splitting large graphs into multiple files"); - + "Enable splitting large graphs into multiple files"); + max_nodes_per_graph = 50; params->SetDefaultParameter("janadot:max_nodes_per_graph", max_nodes_per_graph, - "Maximum number of nodes per graph when splitting"); - + "Maximum number of nodes per graph when splitting"); + max_edges_per_graph = 100; params->SetDefaultParameter("janadot:max_edges_per_graph", max_edges_per_graph, - "Maximum number of edges per graph when splitting"); - + "Maximum number of edges per graph when splitting"); + split_criteria = "size"; params->SetDefaultParameter("janadot:split_criteria", split_criteria, - "Criteria for splitting graphs: size, components, type"); + "Criteria for splitting graphs: size, components, type"); } void JEventProcessorJANADOT::Process(const std::shared_ptr& event) { @@ -102,24 +101,25 @@ void JEventProcessorJANADOT::Process(const std::shared_ptr& event) } } -void JEventProcessorJANADOT::Finish() { - WriteDotFile(); -} +void JEventProcessorJANADOT::Finish() { WriteDotFile(); } void JEventProcessorJANADOT::WriteDotFile() { int total_nodes, total_edges; AnalyzeGraph(total_nodes, total_edges); - - std::cout << "Graph analysis: " << total_nodes << " nodes, " << total_edges << " edges" << std::endl; - + + std::cout << "Graph analysis: " << total_nodes << " nodes, " << total_edges << " edges" + << std::endl; + if (enable_splitting && ShouldSplitGraph(total_nodes, total_edges)) { std::cout << "Graph is large, splitting into multiple files..." << std::endl; WriteSplitDotFiles(); } else { WriteSingleDotFile(output_filename); std::cout << std::endl; - std::cout << "Factory calling information written to \"" << output_filename << "\". To create a graphic" << std::endl; - std::cout << "from this, use the dot program. For example, to make a PDF file do the following:" << std::endl; + std::cout << "Factory calling information written to \"" << output_filename + << "\". To create a graphic" << std::endl; + std::cout << "from this, use the dot program. For example, to make a PDF file do the following:" + << std::endl; std::cout << std::endl; std::cout << " dot -Tpdf " << output_filename << " -o jana.pdf" << std::endl; std::cout << std::endl; @@ -130,13 +130,13 @@ void JEventProcessorJANADOT::WriteDotFile() { void JEventProcessorJANADOT::WriteSingleDotFile(const std::string& filename) { std::cout << "Opening output file \"" << filename << "\"" << std::endl; - + std::ofstream ofs(filename); if (!ofs.is_open()) { std::cerr << "Error: Unable to open file " << filename << " for writing!" << std::endl; return; } - + // Calculate total time for percentages double total_ms = 0.0; for (auto& [link, stats] : call_links) { @@ -151,11 +151,12 @@ void JEventProcessorJANADOT::WriteSingleDotFile(const std::string& filename) { } } if (is_top_level) { - total_ms += stats.from_factory_ms + stats.from_source_ms + - stats.from_cache_ms + stats.data_not_available_ms; + total_ms += stats.from_factory_ms + stats.from_source_ms + stats.from_cache_ms + + stats.data_not_available_ms; } } - if (total_ms == 0.0) total_ms = 1.0; + if (total_ms == 0.0) + total_ms = 1.0; // Write DOT file header ofs << "digraph G {" << std::endl; @@ -163,50 +164,52 @@ void JEventProcessorJANADOT::WriteSingleDotFile(const std::string& filename) { ofs << " node [fontname=\"Arial\", fontsize=10];" << std::endl; ofs << " edge [fontname=\"Arial\", fontsize=8];" << std::endl; ofs << std::endl; - + // Write nodes for (auto& [nametag, fstats] : factory_stats) { double time_in_factory = fstats.time_waited_on - fstats.time_waiting; - double percent = 100.0 * time_in_factory / total_ms; - + double percent = 100.0 * time_in_factory / total_ms; + std::string color = GetNodeColor(fstats.type); std::string shape = GetNodeShape(fstats.type); - + ofs << " \"" << nametag << "\" ["; ofs << "fillcolor=" << color << ", "; ofs << "style=filled, "; ofs << "shape=" << shape << ", "; ofs << "label=\"" << nametag << "\\n"; - ofs << MakeTimeString(time_in_factory) << " (" << std::fixed << std::setprecision(1) << percent << "%)\""; + ofs << MakeTimeString(time_in_factory) << " (" << std::fixed << std::setprecision(1) << percent + << "%)\""; ofs << "];" << std::endl; } - + ofs << std::endl; - + // Write edges for (auto& [link, stats] : call_links) { std::string caller = MakeNametag(link.caller_name, link.caller_tag); std::string callee = MakeNametag(link.callee_name, link.callee_tag); - - unsigned int total_calls = stats.Nfrom_cache + stats.Nfrom_source + - stats.Nfrom_factory + stats.Ndata_not_available; - double total_time = stats.from_cache_ms + stats.from_source_ms + - stats.from_factory_ms + stats.data_not_available_ms; + + unsigned int total_calls = + stats.Nfrom_cache + stats.Nfrom_source + stats.Nfrom_factory + stats.Ndata_not_available; + double total_time = stats.from_cache_ms + stats.from_source_ms + stats.from_factory_ms + + stats.data_not_available_ms; double percent = 100.0 * total_time / total_ms; - + ofs << " \"" << caller << "\" -> \"" << callee << "\" ["; ofs << "label=\"" << total_calls << " calls\\n"; - ofs << MakeTimeString(total_time) << " (" << std::fixed << std::setprecision(1) << percent << "%)\""; + ofs << MakeTimeString(total_time) << " (" << std::fixed << std::setprecision(1) << percent + << "%)\""; ofs << "];" << std::endl; } - + ofs << "}" << std::endl; ofs.close(); } void JEventProcessorJANADOT::WriteSplitDotFiles() { std::vector> node_groups; - + if (split_criteria == "components") { node_groups = SplitGraphByConnectedComponents(); } else if (split_criteria == "type") { @@ -214,150 +217,157 @@ void JEventProcessorJANADOT::WriteSplitDotFiles() { } else { // default to "size" node_groups = SplitGraphBySize(); } - + std::cout << "Splitting graph into " << node_groups.size() << " subgraphs" << std::endl; - + for (size_t i = 0; i < node_groups.size(); i++) { // Create filename for this subgraph std::string base_filename = output_filename; - size_t dot_pos = base_filename.find_last_of('.'); + size_t dot_pos = base_filename.find_last_of('.'); if (dot_pos != std::string::npos) { base_filename = base_filename.substr(0, dot_pos); } - + std::stringstream ss; ss << base_filename << "_part" << std::setfill('0') << std::setw(3) << (i + 1) << ".dot"; std::string filename = ss.str(); - + WriteSplitDotFile(filename, node_groups[i]); } - + // Write an index file explaining the split WriteIndexFile(node_groups.size()); } -void JEventProcessorJANADOT::WriteSplitDotFile(const std::string& filename, const std::set& nodes) { +void JEventProcessorJANADOT::WriteSplitDotFile(const std::string& filename, + const std::set& nodes) { std::ofstream ofs(filename); if (!ofs.is_open()) { std::cerr << "Error: Unable to open file " << filename << " for writing!" << std::endl; return; } - + // Calculate total time for this subgraph double total_ms = 0.0; for (auto& [link, stats] : call_links) { std::string caller = MakeNametag(link.caller_name, link.caller_tag); if (nodes.find(caller) != nodes.end()) { - total_ms += stats.from_factory_ms + stats.from_source_ms + - stats.from_cache_ms + stats.data_not_available_ms; + total_ms += stats.from_factory_ms + stats.from_source_ms + stats.from_cache_ms + + stats.data_not_available_ms; } } - if (total_ms == 0.0) total_ms = 1.0; + if (total_ms == 0.0) + total_ms = 1.0; // Write DOT file header ofs << "digraph G {" << std::endl; ofs << " rankdir=TB;" << std::endl; ofs << " node [fontname=\"Arial\", fontsize=10];" << std::endl; ofs << " edge [fontname=\"Arial\", fontsize=8];" << std::endl; - ofs << " label=\"EICrecon Call Graph (Part " << filename.substr(filename.find("part")) << ")\";" << std::endl; + ofs << " label=\"EICrecon Call Graph (Part " << filename.substr(filename.find("part")) << ")\";" + << std::endl; ofs << " labelloc=\"t\";" << std::endl; ofs << std::endl; - + // Write nodes (only those in this group) for (const std::string& nametag : nodes) { auto fstats_it = factory_stats.find(nametag); - if (fstats_it == factory_stats.end()) continue; - + if (fstats_it == factory_stats.end()) + continue; + const FactoryCallStats& fstats = fstats_it->second; - double time_in_factory = fstats.time_waited_on - fstats.time_waiting; - double percent = 100.0 * time_in_factory / total_ms; - + double time_in_factory = fstats.time_waited_on - fstats.time_waiting; + double percent = 100.0 * time_in_factory / total_ms; + std::string color = GetNodeColor(fstats.type); std::string shape = GetNodeShape(fstats.type); - + ofs << " \"" << nametag << "\" ["; ofs << "fillcolor=" << color << ", "; ofs << "style=filled, "; ofs << "shape=" << shape << ", "; ofs << "label=\"" << nametag << "\\n"; - ofs << MakeTimeString(time_in_factory) << " (" << std::fixed << std::setprecision(1) << percent << "%)\""; + ofs << MakeTimeString(time_in_factory) << " (" << std::fixed << std::setprecision(1) << percent + << "%)\""; ofs << "];" << std::endl; } - + ofs << std::endl; - + // Write edges (only those within this group) for (auto& [link, stats] : call_links) { std::string caller = MakeNametag(link.caller_name, link.caller_tag); std::string callee = MakeNametag(link.callee_name, link.callee_tag); - + // Only include edges where both nodes are in this group if (nodes.find(caller) == nodes.end() || nodes.find(callee) == nodes.end()) { continue; } - - unsigned int total_calls = stats.Nfrom_cache + stats.Nfrom_source + - stats.Nfrom_factory + stats.Ndata_not_available; - double total_time = stats.from_cache_ms + stats.from_source_ms + - stats.from_factory_ms + stats.data_not_available_ms; + + unsigned int total_calls = + stats.Nfrom_cache + stats.Nfrom_source + stats.Nfrom_factory + stats.Ndata_not_available; + double total_time = stats.from_cache_ms + stats.from_source_ms + stats.from_factory_ms + + stats.data_not_available_ms; double percent = 100.0 * total_time / total_ms; - + ofs << " \"" << caller << "\" -> \"" << callee << "\" ["; ofs << "label=\"" << total_calls << " calls\\n"; - ofs << MakeTimeString(total_time) << " (" << std::fixed << std::setprecision(1) << percent << "%)\""; + ofs << MakeTimeString(total_time) << " (" << std::fixed << std::setprecision(1) << percent + << "%)\""; ofs << "];" << std::endl; } - + ofs << "}" << std::endl; ofs.close(); } void JEventProcessorJANADOT::WriteIndexFile(int num_parts) { std::string base_filename = output_filename; - size_t dot_pos = base_filename.find_last_of('.'); + size_t dot_pos = base_filename.find_last_of('.'); if (dot_pos != std::string::npos) { base_filename = base_filename.substr(0, dot_pos); } - + std::string index_filename = base_filename + "_index.txt"; std::ofstream ofs(index_filename); if (!ofs.is_open()) { - std::cerr << "Error: Unable to open index file " << index_filename << " for writing!" << std::endl; + std::cerr << "Error: Unable to open index file " << index_filename << " for writing!" + << std::endl; return; } - + ofs << "EICrecon Call Graph Split Information" << std::endl; ofs << "=====================================" << std::endl; ofs << std::endl; ofs << "The call graph was too large for efficient processing by graphviz," << std::endl; ofs << "so it has been split into " << num_parts << " separate DOT files:" << std::endl; ofs << std::endl; - + for (int i = 1; i <= num_parts; i++) { std::stringstream ss; ss << base_filename << "_part" << std::setfill('0') << std::setw(3) << i << ".dot"; ofs << " " << ss.str() << std::endl; } - + ofs << std::endl; ofs << "To generate PDF files from each part:" << std::endl; ofs << std::endl; - + for (int i = 1; i <= num_parts; i++) { std::stringstream ss_dot, ss_pdf; ss_dot << base_filename << "_part" << std::setfill('0') << std::setw(3) << i << ".dot"; ss_pdf << base_filename << "_part" << std::setfill('0') << std::setw(3) << i << ".pdf"; ofs << " dot -Tpdf " << ss_dot.str() << " -o " << ss_pdf.str() << std::endl; } - + ofs << std::endl; ofs << "Configuration used:" << std::endl; ofs << " Split criteria: " << split_criteria << std::endl; ofs << " Max nodes per graph: " << max_nodes_per_graph << std::endl; ofs << " Max edges per graph: " << max_edges_per_graph << std::endl; - + ofs.close(); - + std::cout << std::endl; std::cout << "Factory calling information written to " << num_parts << " files." << std::endl; std::cout << "See \"" << index_filename << "\" for details on processing the files." << std::endl; @@ -385,7 +395,8 @@ std::string JEventProcessorJANADOT::MakeNametag(const std::string& name, const s return nametag; } -JEventProcessorJANADOT::node_type JEventProcessorJANADOT::GetNodeType(const std::string& name, const std::string& tag) { +JEventProcessorJANADOT::node_type JEventProcessorJANADOT::GetNodeType(const std::string& name, + const std::string& tag) { // Simple heuristics to determine node type based on name patterns if (name.find("Processor") != std::string::npos || name.find("PROCESSOR") != std::string::npos) { return kProcessor; @@ -393,7 +404,8 @@ JEventProcessorJANADOT::node_type JEventProcessorJANADOT::GetNodeType(const std: return kSource; } else if (name.find("Cache") != std::string::npos || name.find("CACHE") != std::string::npos) { return kCache; - } else if (!tag.empty() || name.find("Factory") != std::string::npos || name.find("edm4") != std::string::npos) { + } else if (!tag.empty() || name.find("Factory") != std::string::npos || + name.find("edm4") != std::string::npos) { return kFactory; } return kDefault; @@ -401,21 +413,31 @@ JEventProcessorJANADOT::node_type JEventProcessorJANADOT::GetNodeType(const std: std::string JEventProcessorJANADOT::GetNodeColor(node_type type) { switch (type) { - case kProcessor: return "lightgreen"; - case kFactory: return "lightblue"; - case kCache: return "yellow"; - case kSource: return "lightcoral"; - default: return "white"; + case kProcessor: + return "lightgreen"; + case kFactory: + return "lightblue"; + case kCache: + return "yellow"; + case kSource: + return "lightcoral"; + default: + return "white"; } } std::string JEventProcessorJANADOT::GetNodeShape(node_type type) { switch (type) { - case kProcessor: return "ellipse"; - case kFactory: return "box"; - case kCache: return "diamond"; - case kSource: return "trapezium"; - default: return "ellipse"; + case kProcessor: + return "ellipse"; + case kFactory: + return "box"; + case kCache: + return "diamond"; + case kSource: + return "trapezium"; + default: + return "ellipse"; } } @@ -431,10 +453,10 @@ bool JEventProcessorJANADOT::ShouldSplitGraph(int total_nodes, int total_edges) std::vector> JEventProcessorJANADOT::SplitGraphBySize() { std::vector> groups; std::set current_group; - + int current_nodes = 0; int current_edges = 0; - + // Simple greedy algorithm: add nodes to current group until limits are reached for (auto& [nametag, fstats] : factory_stats) { // Count edges involving this node @@ -444,32 +466,31 @@ std::vector> JEventProcessorJANADOT::SplitGraphBySize() { std::string callee = MakeNametag(link.callee_name, link.callee_tag); if (caller == nametag || callee == nametag) { // Only count edge if both nodes are in current group (or this is the first node) - if (current_group.empty() || - current_group.find(caller) != current_group.end() || + if (current_group.empty() || current_group.find(caller) != current_group.end() || current_group.find(callee) != current_group.end()) { node_edges++; } } } - + // Check if adding this node would exceed limits - if (current_nodes > 0 && - (current_nodes + 1 > max_nodes_per_graph || current_edges + node_edges > max_edges_per_graph)) { + if (current_nodes > 0 && (current_nodes + 1 > max_nodes_per_graph || + current_edges + node_edges > max_edges_per_graph)) { groups.push_back(current_group); current_group.clear(); current_nodes = 0; current_edges = 0; } - + current_group.insert(nametag); current_nodes++; current_edges += node_edges; } - + if (!current_group.empty()) { groups.push_back(current_group); } - + // If we only have one group, return it as-is (even if it's large) if (groups.empty()) { std::set all_nodes; @@ -478,19 +499,19 @@ std::vector> JEventProcessorJANADOT::SplitGraphBySize() { } groups.push_back(all_nodes); } - + return groups; } std::vector> JEventProcessorJANADOT::SplitGraphByConnectedComponents() { // Implementation of connected components finding using Union-Find std::map parent; - + // Initialize each node as its own parent for (auto& [nametag, fstats] : factory_stats) { parent[nametag] = nametag; } - + // Union-Find helper functions std::function find = [&](const std::string& x) -> std::string { if (parent[x] != x) { @@ -498,7 +519,7 @@ std::vector> JEventProcessorJANADOT::SplitGraphByConnected } return parent[x]; }; - + auto unite = [&](const std::string& x, const std::string& y) { std::string px = find(x); std::string py = find(y); @@ -506,37 +527,37 @@ std::vector> JEventProcessorJANADOT::SplitGraphByConnected parent[px] = py; } }; - + // Connect nodes that have edges between them for (auto& [link, stats] : call_links) { std::string caller = MakeNametag(link.caller_name, link.caller_tag); std::string callee = MakeNametag(link.callee_name, link.callee_tag); unite(caller, callee); } - + // Group nodes by their root parent std::map> components; for (auto& [nametag, fstats] : factory_stats) { components[find(nametag)].insert(nametag); } - + // Convert to vector of sets std::vector> groups; for (auto& [root, component] : components) { groups.push_back(component); } - + return groups; } std::vector> JEventProcessorJANADOT::SplitGraphByType() { std::map> type_groups; - + // Group nodes by their type for (auto& [nametag, fstats] : factory_stats) { type_groups[fstats.type].insert(nametag); } - + // Convert to vector of sets std::vector> groups; for (auto& [type, group] : type_groups) { @@ -544,6 +565,6 @@ std::vector> JEventProcessorJANADOT::SplitGraphByType() { groups.push_back(group); } } - + return groups; -} \ No newline at end of file +} diff --git a/src/utilities/janadot/JEventProcessorJANADOT.h b/src/utilities/janadot/JEventProcessorJANADOT.h index 8b5113d856..92d6b07ba4 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.h +++ b/src/utilities/janadot/JEventProcessorJANADOT.h @@ -75,9 +75,7 @@ class JEventProcessorJANADOT : public JEventProcessor { }; public: - JEventProcessorJANADOT() : JEventProcessor() { - SetTypeName("JEventProcessorJANADOT"); - }; + JEventProcessorJANADOT() : JEventProcessor() { SetTypeName("JEventProcessorJANADOT"); }; void Init() override; void BeginRun(const std::shared_ptr& /* event */) override {}; @@ -87,24 +85,24 @@ class JEventProcessorJANADOT : public JEventProcessor { private: std::mutex mutex; - + std::map call_links; std::map factory_stats; - - // Configuration parameters + + // Configuration parameters std::string output_filename; bool enable_splitting; int max_nodes_per_graph; int max_edges_per_graph; std::string split_criteria; - + // Helper methods std::string MakeTimeString(double time_in_ms); std::string MakeNametag(const std::string& name, const std::string& tag); node_type GetNodeType(const std::string& name, const std::string& tag); std::string GetNodeColor(node_type type); std::string GetNodeShape(node_type type); - + // DOT file generation methods void WriteDotFile(); void WriteSingleDotFile(const std::string& filename); @@ -114,8 +112,8 @@ class JEventProcessorJANADOT : public JEventProcessor { std::vector> SplitGraphByConnectedComponents(); std::vector> SplitGraphBySize(); std::vector> SplitGraphByType(); - + // Graph analysis methods void AnalyzeGraph(int& total_nodes, int& total_edges); bool ShouldSplitGraph(int total_nodes, int total_edges); -}; \ No newline at end of file +}; diff --git a/src/utilities/janadot/README.md b/src/utilities/janadot/README.md index 42905c233f..3306fb5953 100644 --- a/src/utilities/janadot/README.md +++ b/src/utilities/janadot/README.md @@ -19,7 +19,7 @@ eicrecon -Pplugins=janadot sim_file.edm4hep.root The plugin supports several configuration parameters: - `janadot:output_file` (default: "jana.dot") - Output DOT filename -- `janadot:enable_splitting` (default: true) - Enable splitting large graphs into multiple files +- `janadot:enable_splitting` (default: true) - Enable splitting large graphs into multiple files - `janadot:max_nodes_per_graph` (default: 50) - Maximum number of nodes per graph when splitting - `janadot:max_edges_per_graph` (default: 100) - Maximum number of edges per graph when splitting - `janadot:split_criteria` (default: "size") - Criteria for splitting graphs: size, components, type @@ -50,8 +50,8 @@ dot -Tpdf jana_part002.dot -o jana_part002.pdf ### Size-based Splitting Nodes are grouped to keep within the specified limits of nodes and edges per graph. -### Component-based Splitting +### Component-based Splitting Uses connected components analysis to group nodes that are connected by call relationships. ### Type-based Splitting -Groups nodes by their type (Processor, Factory, Source, etc.). \ No newline at end of file +Groups nodes by their type (Processor, Factory, Source, etc.). diff --git a/src/utilities/janadot/janadot.cc b/src/utilities/janadot/janadot.cc index cf56c59142..f055bd8317 100644 --- a/src/utilities/janadot/janadot.cc +++ b/src/utilities/janadot/janadot.cc @@ -15,4 +15,4 @@ void InitPlugin(JApplication* app) { app->Add(new JEventProcessorJANADOT()); app->GetJParameterManager()->SetParameter("RECORD_CALL_STACK", true); } -} \ No newline at end of file +} From da541659d5887a0bb7c5fba6cb4fb63827351fed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 14:14:58 +0000 Subject: [PATCH 04/29] Implement plugin-based janadot splitting with period-separated naming Co-authored-by: wdconinc <4656391+wdconinc@users.noreply.github.com> --- .github/workflows/linux-eic-shell.yml | 51 ++- .../janadot/JEventProcessorJANADOT.cc | 330 +++++++++++++++++- .../janadot/JEventProcessorJANADOT.h | 5 + 3 files changed, 373 insertions(+), 13 deletions(-) diff --git a/.github/workflows/linux-eic-shell.yml b/.github/workflows/linux-eic-shell.yml index 824bafa87a..943f760f3c 100644 --- a/.github/workflows/linux-eic-shell.yml +++ b/.github/workflows/linux-eic-shell.yml @@ -709,7 +709,7 @@ jobs: export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins prmon --json-summary rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.prmon.json -- \ - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,janadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,janadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Pjanadot:split_criteria=plugin - uses: actions/upload-artifact@v4 with: name: rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root @@ -726,9 +726,18 @@ jobs: platform-release: "${{ env.platform }}:${{ env.release }}" setup: "/opt/detector/epic-${{ env.detector-version }}/bin/thisepic.sh" run: | - mv jana.dot rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.dot - sed '/rank=sink/s/"podio::Frame";//; /podio::Frame/d; /rank=source/s/"JEventProcessorPODIO";//; /JEventProcessorPODIO/d' *.dot | dot -Tsvg > jana.svg - mv jana.svg rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.svg + # Rename main file and convert all dot files to SVG + if [ -f jana.dot ]; then + mv jana.dot rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.dot + fi + + # Convert all dot files to SVG (both main and plugin-specific files) + for dotfile in *.dot; do + if [ -f "$dotfile" ]; then + svgfile="${dotfile%.dot}.svg" + sed '/rank=sink/s/"podio::Frame";//; /podio::Frame/d; /rank=source/s/"JEventProcessorPODIO";//; /JEventProcessorPODIO/d' "$dotfile" | dot -Tsvg > "$svgfile" + fi + done continue-on-error: true - uses: actions/upload-artifact@v4 with: @@ -808,7 +817,7 @@ jobs: export DETECTOR_CONFIG=${DETECTOR}_${{ matrix.detector_config }} export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4hep.root -Ppodio:output_collections=EcalLumiSpecRawHits,EcalLumiSpecRecHits,EcalLumiSpecClusters,EcalLumiSpecClusterAssociations -PLUMISPECCAL:EcalLumiSpecIslandProtoClusters:splitCluster=1 -Pplugins=dump_flags,janadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4hep.root -Ppodio:output_collections=EcalLumiSpecRawHits,EcalLumiSpecRecHits,EcalLumiSpecClusters,EcalLumiSpecClusterAssociations -PLUMISPECCAL:EcalLumiSpecIslandProtoClusters:splitCluster=1 -Pplugins=dump_flags,janadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Pjanadot:split_criteria=plugin - uses: actions/upload-artifact@v4 with: name: rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root @@ -820,9 +829,18 @@ jobs: platform-release: "${{ env.platform }}:${{ env.release }}" setup: "/opt/detector/epic-${{ env.detector-version }}/bin/thisepic.sh" run: | - mv jana.dot rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.dot - sed '/rank=sink/s/"podio::Frame";//; /podio::Frame/d; /rank=source/s/"JEventProcessorPODIO";//; /JEventProcessorPODIO/d' *.dot | dot -Tsvg > jana.svg - mv jana.svg rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.svg + # Rename main file and convert all dot files to SVG + if [ -f jana.dot ]; then + mv jana.dot rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.dot + fi + + # Convert all dot files to SVG (both main and plugin-specific files) + for dotfile in *.dot; do + if [ -f "$dotfile" ]; then + svgfile="${dotfile%.dot}.svg" + sed '/rank=sink/s/"podio::Frame";//; /podio::Frame/d; /rank=source/s/"JEventProcessorPODIO";//; /JEventProcessorPODIO/d' "$dotfile" | dot -Tsvg > "$svgfile" + fi + done continue-on-error: true - uses: actions/upload-artifact@v4 with: @@ -911,7 +929,7 @@ jobs: export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins prmon --json-summary rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.prmon.json -- \ - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{matrix.nthreads > 1 && format('-Pnthreads={0}', matrix.nthreads) || ''}} -Ppodio:output_file=rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root sim_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}.edm4hep.root -Pacts:WriteObj=true -Pacts:WritePly=true -Pplugins=janadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{matrix.nthreads > 1 && format('-Pnthreads={0}', matrix.nthreads) || ''}} -Ppodio:output_file=rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root sim_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}.edm4hep.root -Pacts:WriteObj=true -Pacts:WritePly=true -Pplugins=janadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Pjanadot:split_criteria=plugin - uses: actions/upload-artifact@v4 with: name: rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root @@ -928,9 +946,18 @@ jobs: platform-release: "${{ env.platform }}:${{ env.release }}" setup: "/opt/detector/epic-${{ env.detector-version }}/bin/thisepic.sh" run: | - mv jana.dot rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.dot - sed '/rank=sink/s/"podio::Frame";//; /podio::Frame/d; /rank=source/s/"JEventProcessorPODIO";//; /JEventProcessorPODIO/d' *.dot | dot -Tsvg > jana.svg - mv jana.svg rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.svg + # Rename main file and convert all dot files to SVG + if [ -f jana.dot ]; then + mv jana.dot rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.dot + fi + + # Convert all dot files to SVG (both main and plugin-specific files) + for dotfile in *.dot; do + if [ -f "$dotfile" ]; then + svgfile="${dotfile%.dot}.svg" + sed '/rank=sink/s/"podio::Frame";//; /podio::Frame/d; /rank=source/s/"JEventProcessorPODIO";//; /JEventProcessorPODIO/d' "$dotfile" | dot -Tsvg > "$svgfile" + fi + done continue-on-error: true - uses: actions/upload-artifact@v4 with: diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index c5077cb483..2aa9dcd056 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -15,6 +15,7 @@ #include #include #include +#include void JEventProcessorJANADOT::Init() { // Get parameter manager @@ -38,7 +39,7 @@ void JEventProcessorJANADOT::Init() { split_criteria = "size"; params->SetDefaultParameter("janadot:split_criteria", split_criteria, - "Criteria for splitting graphs: size, components, type"); + "Criteria for splitting graphs: size, components, type, plugin"); } void JEventProcessorJANADOT::Process(const std::shared_ptr& event) { @@ -208,6 +209,11 @@ void JEventProcessorJANADOT::WriteSingleDotFile(const std::string& filename) { } void JEventProcessorJANADOT::WriteSplitDotFiles() { + if (split_criteria == "plugin") { + WritePluginDotFiles(); + return; + } + std::vector> node_groups; if (split_criteria == "components") { @@ -568,3 +574,325 @@ std::vector> JEventProcessorJANADOT::SplitGraphByType() { return groups; } + +void JEventProcessorJANADOT::WritePluginDotFiles() { + auto plugin_groups = SplitGraphByPlugin(); + + std::cout << "Splitting graph into " << plugin_groups.size() << " plugin-based subgraphs" << std::endl; + + // Write individual plugin dot files + for (auto& [plugin_name, nodes] : plugin_groups) { + WritePluginDotFile(plugin_name, nodes); + } + + // Write overall summary graph showing inter-plugin connections + WriteOverallDotFile(plugin_groups); + + std::cout << std::endl; + std::cout << "Factory calling information written to " << plugin_groups.size() << " plugin files plus overall summary." << std::endl; + std::cout << "Plugin files use period-separated naming (e.g., jana.tracking.dot)." << std::endl; + std::cout << "Overall summary in \"" << output_filename << "\" shows inter-plugin connections." << std::endl; + std::cout << std::endl; +} + +void JEventProcessorJANADOT::WritePluginDotFile(const std::string& plugin_name, const std::set& nodes) { + // Create filename using period-separated plugin name + std::string base_filename = output_filename; + size_t dot_pos = base_filename.find_last_of('.'); + if (dot_pos != std::string::npos) { + base_filename = base_filename.substr(0, dot_pos); + } + + std::string filename = base_filename + "." + plugin_name + ".dot"; + + std::ofstream ofs(filename); + if (!ofs.is_open()) { + std::cerr << "Error: Unable to open file " << filename << " for writing!" << std::endl; + return; + } + + // Calculate total time for this plugin + double total_ms = 0.0; + for (auto& [link, stats] : call_links) { + std::string caller = MakeNametag(link.caller_name, link.caller_tag); + if (nodes.find(caller) != nodes.end()) { + total_ms += stats.from_factory_ms + stats.from_source_ms + stats.from_cache_ms + + stats.data_not_available_ms; + } + } + if (total_ms == 0.0) + total_ms = 1.0; + + // Write DOT file header + ofs << "digraph G {" << std::endl; + ofs << " rankdir=TB;" << std::endl; + ofs << " node [fontname=\"Arial\", fontsize=10];" << std::endl; + ofs << " edge [fontname=\"Arial\", fontsize=8];" << std::endl; + ofs << " label=\"EICrecon Call Graph - " << plugin_name << " Plugin\";" << std::endl; + ofs << " labelloc=\"t\";" << std::endl; + ofs << std::endl; + + // Write nodes (only those in this plugin) + for (const std::string& nametag : nodes) { + auto fstats_it = factory_stats.find(nametag); + if (fstats_it == factory_stats.end()) + continue; + + const FactoryCallStats& fstats = fstats_it->second; + double time_in_factory = fstats.time_waited_on - fstats.time_waiting; + double percent = 100.0 * time_in_factory / total_ms; + + std::string color = GetNodeColor(fstats.type); + std::string shape = GetNodeShape(fstats.type); + + ofs << " \"" << nametag << "\" ["; + ofs << "fillcolor=" << color << ", "; + ofs << "style=filled, "; + ofs << "shape=" << shape << ", "; + ofs << "label=\"" << nametag << "\\n"; + ofs << MakeTimeString(time_in_factory) << " (" << std::fixed << std::setprecision(1) << percent + << "%)\""; + ofs << "];" << std::endl; + } + + ofs << std::endl; + + // Write edges (only those within this plugin) + for (auto& [link, stats] : call_links) { + std::string caller = MakeNametag(link.caller_name, link.caller_tag); + std::string callee = MakeNametag(link.callee_name, link.callee_tag); + + // Only include edges where both nodes are in this plugin + if (nodes.find(caller) == nodes.end() || nodes.find(callee) == nodes.end()) { + continue; + } + + unsigned int total_calls = + stats.Nfrom_cache + stats.Nfrom_source + stats.Nfrom_factory + stats.Ndata_not_available; + double total_time = stats.from_cache_ms + stats.from_source_ms + stats.from_factory_ms + + stats.data_not_available_ms; + double percent = 100.0 * total_time / total_ms; + + ofs << " \"" << caller << "\" -> \"" << callee << "\" ["; + ofs << "label=\"" << total_calls << " calls\\n"; + ofs << MakeTimeString(total_time) << " (" << std::fixed << std::setprecision(1) << percent + << "%)\""; + ofs << "];" << std::endl; + } + + ofs << "}" << std::endl; + ofs.close(); +} + +void JEventProcessorJANADOT::WriteOverallDotFile(const std::map>& plugin_groups) { + std::ofstream ofs(output_filename); + if (!ofs.is_open()) { + std::cerr << "Error: Unable to open file " << output_filename << " for writing!" << std::endl; + return; + } + + // Calculate total time for percentages + double total_ms = 0.0; + for (auto& [link, stats] : call_links) { + std::string caller = MakeNametag(link.caller_name, link.caller_tag); + // Only count top-level callers (those not called by others) + bool is_top_level = true; + for (auto& [other_link, other_stats] : call_links) { + std::string other_callee = MakeNametag(other_link.callee_name, other_link.callee_tag); + if (other_callee == caller) { + is_top_level = false; + break; + } + } + if (is_top_level) { + total_ms += stats.from_factory_ms + stats.from_source_ms + stats.from_cache_ms + + stats.data_not_available_ms; + } + } + if (total_ms == 0.0) + total_ms = 1.0; + + // Write DOT file header + ofs << "digraph G {" << std::endl; + ofs << " rankdir=TB;" << std::endl; + ofs << " node [fontname=\"Arial\", fontsize=12];" << std::endl; + ofs << " edge [fontname=\"Arial\", fontsize=10];" << std::endl; + ofs << " label=\"EICrecon Overall Call Graph - Inter-Plugin Connections\";" << std::endl; + ofs << " labelloc=\"t\";" << std::endl; + ofs << std::endl; + + // Create plugin nodes (aggregate statistics per plugin) + std::map plugin_times; + std::map plugin_node_counts; + + for (auto& [plugin_name, nodes] : plugin_groups) { + double plugin_time = 0.0; + int node_count = 0; + + for (const std::string& nametag : nodes) { + auto fstats_it = factory_stats.find(nametag); + if (fstats_it != factory_stats.end()) { + const FactoryCallStats& fstats = fstats_it->second; + plugin_time += fstats.time_waited_on - fstats.time_waiting; + node_count++; + } + } + + plugin_times[plugin_name] = plugin_time; + plugin_node_counts[plugin_name] = node_count; + + double percent = 100.0 * plugin_time / total_ms; + + ofs << " \"" << plugin_name << "\" ["; + ofs << "fillcolor=lightsteelblue, "; + ofs << "style=filled, "; + ofs << "shape=box, "; + ofs << "label=\"" << plugin_name << "\\n"; + ofs << node_count << " components\\n"; + ofs << MakeTimeString(plugin_time) << " (" << std::fixed << std::setprecision(1) << percent + << "%)\""; + ofs << "];" << std::endl; + } + + ofs << std::endl; + + // Create edges between plugins (when there are calls between different plugins) + std::map, std::pair> inter_plugin_calls; + + for (auto& [link, stats] : call_links) { + std::string caller = MakeNametag(link.caller_name, link.caller_tag); + std::string callee = MakeNametag(link.callee_name, link.callee_tag); + + std::string caller_plugin = ExtractPluginName(caller); + std::string callee_plugin = ExtractPluginName(callee); + + // Only show inter-plugin connections + if (caller_plugin != callee_plugin && !caller_plugin.empty() && !callee_plugin.empty()) { + auto key = std::make_pair(caller_plugin, callee_plugin); + + unsigned int total_calls = + stats.Nfrom_cache + stats.Nfrom_source + stats.Nfrom_factory + stats.Ndata_not_available; + double total_time = stats.from_cache_ms + stats.from_source_ms + stats.from_factory_ms + + stats.data_not_available_ms; + + inter_plugin_calls[key].first += total_calls; + inter_plugin_calls[key].second += total_time; + } + } + + // Write inter-plugin edges + for (auto& [plugins, call_data] : inter_plugin_calls) { + int total_calls = call_data.first; + double total_time = call_data.second; + double percent = 100.0 * total_time / total_ms; + + ofs << " \"" << plugins.first << "\" -> \"" << plugins.second << "\" ["; + ofs << "label=\"" << total_calls << " calls\\n"; + ofs << MakeTimeString(total_time) << " (" << std::fixed << std::setprecision(1) << percent + << "%)\", "; + ofs << "penwidth=2"; + ofs << "];" << std::endl; + } + + ofs << "}" << std::endl; + ofs.close(); +} + +std::map> JEventProcessorJANADOT::SplitGraphByPlugin() { + std::map> plugin_groups; + + // Group nodes by their detected plugin + for (auto& [nametag, fstats] : factory_stats) { + std::string plugin = ExtractPluginName(nametag); + plugin_groups[plugin].insert(nametag); + } + + return plugin_groups; +} + +std::string JEventProcessorJANADOT::ExtractPluginName(const std::string& nametag) { + // Try to extract meaningful plugin names from component names + + // Common EIC patterns + if (nametag.find("Ecal") != std::string::npos) { + if (nametag.find("Barrel") != std::string::npos) return "ecal_barrel"; + if (nametag.find("Endcap") != std::string::npos) return "ecal_endcap"; + if (nametag.find("Insert") != std::string::npos) return "ecal_insert"; + if (nametag.find("LumiSpec") != std::string::npos) return "ecal_lumispec"; + return "ecal"; + } + + if (nametag.find("Hcal") != std::string::npos) { + if (nametag.find("Barrel") != std::string::npos) return "hcal_barrel"; + if (nametag.find("Endcap") != std::string::npos) return "hcal_endcap"; + if (nametag.find("Insert") != std::string::npos) return "hcal_insert"; + return "hcal"; + } + + if (nametag.find("Track") != std::string::npos || nametag.find("track") != std::string::npos) { + if (nametag.find("CKF") != std::string::npos) return "tracking_ckf"; + if (nametag.find("Seed") != std::string::npos) return "tracking_seeding"; + if (nametag.find("Vertex") != std::string::npos) return "tracking_vertex"; + return "tracking"; + } + + if (nametag.find("Cluster") != std::string::npos || nametag.find("cluster") != std::string::npos) { + return "clustering"; + } + + if (nametag.find("PID") != std::string::npos || nametag.find("Pid") != std::string::npos) { + return "pid"; + } + + if (nametag.find("Jet") != std::string::npos || nametag.find("jet") != std::string::npos) { + return "jets"; + } + + if (nametag.find("Truth") != std::string::npos || nametag.find("MC") != std::string::npos) { + return "truth"; + } + + if (nametag.find("B0") != std::string::npos || nametag.find("ZDC") != std::string::npos) { + return "far_detectors"; + } + + if (nametag.find("RICH") != std::string::npos) { + return "rich"; + } + + if (nametag.find("TOF") != std::string::npos) { + return "tof"; + } + + if (nametag.find("DIRC") != std::string::npos) { + return "dirc"; + } + + if (nametag.find("GEM") != std::string::npos || nametag.find("MPGD") != std::string::npos) { + return "gem_tracking"; + } + + if (nametag.find("Silicon") != std::string::npos || nametag.find("Pixel") != std::string::npos) { + return "silicon_tracking"; + } + + if (nametag.find("Processor") != std::string::npos) { + return "processors"; + } + + if (nametag.find("Source") != std::string::npos) { + return "io"; + } + + // Default fallback - try to use first part of name + size_t colon_pos = nametag.find(':'); + if (colon_pos != std::string::npos) { + std::string base_name = nametag.substr(0, colon_pos); + // Convert to lowercase for consistency + std::transform(base_name.begin(), base_name.end(), base_name.begin(), ::tolower); + return base_name; + } + + // Final fallback + return "misc"; +} diff --git a/src/utilities/janadot/JEventProcessorJANADOT.h b/src/utilities/janadot/JEventProcessorJANADOT.h index 92d6b07ba4..8b6a6f6845 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.h +++ b/src/utilities/janadot/JEventProcessorJANADOT.h @@ -108,10 +108,15 @@ class JEventProcessorJANADOT : public JEventProcessor { void WriteSingleDotFile(const std::string& filename); void WriteSplitDotFiles(); void WriteSplitDotFile(const std::string& filename, const std::set& nodes); + void WritePluginDotFiles(); + void WritePluginDotFile(const std::string& plugin_name, const std::set& nodes); + void WriteOverallDotFile(const std::map>& plugin_groups); void WriteIndexFile(int num_parts); std::vector> SplitGraphByConnectedComponents(); std::vector> SplitGraphBySize(); std::vector> SplitGraphByType(); + std::map> SplitGraphByPlugin(); + std::string ExtractPluginName(const std::string& nametag); // Graph analysis methods void AnalyzeGraph(int& total_nodes, int& total_edges); From 8948d9f302c1e43c6deec50821fb42acd05eee81 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Aug 2025 14:15:36 +0000 Subject: [PATCH 05/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .github/workflows/linux-eic-shell.yml | 6 +- .../janadot/JEventProcessorJANADOT.cc | 124 ++++++++++-------- 2 files changed, 73 insertions(+), 57 deletions(-) diff --git a/.github/workflows/linux-eic-shell.yml b/.github/workflows/linux-eic-shell.yml index 943f760f3c..dc92cc2a46 100644 --- a/.github/workflows/linux-eic-shell.yml +++ b/.github/workflows/linux-eic-shell.yml @@ -730,7 +730,7 @@ jobs: if [ -f jana.dot ]; then mv jana.dot rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.dot fi - + # Convert all dot files to SVG (both main and plugin-specific files) for dotfile in *.dot; do if [ -f "$dotfile" ]; then @@ -833,7 +833,7 @@ jobs: if [ -f jana.dot ]; then mv jana.dot rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.dot fi - + # Convert all dot files to SVG (both main and plugin-specific files) for dotfile in *.dot; do if [ -f "$dotfile" ]; then @@ -950,7 +950,7 @@ jobs: if [ -f jana.dot ]; then mv jana.dot rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.dot fi - + # Convert all dot files to SVG (both main and plugin-specific files) for dotfile in *.dot; do if [ -f "$dotfile" ]; then diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index 2aa9dcd056..09f911a50d 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -577,8 +577,9 @@ std::vector> JEventProcessorJANADOT::SplitGraphByType() { void JEventProcessorJANADOT::WritePluginDotFiles() { auto plugin_groups = SplitGraphByPlugin(); - - std::cout << "Splitting graph into " << plugin_groups.size() << " plugin-based subgraphs" << std::endl; + + std::cout << "Splitting graph into " << plugin_groups.size() << " plugin-based subgraphs" + << std::endl; // Write individual plugin dot files for (auto& [plugin_name, nodes] : plugin_groups) { @@ -587,24 +588,27 @@ void JEventProcessorJANADOT::WritePluginDotFiles() { // Write overall summary graph showing inter-plugin connections WriteOverallDotFile(plugin_groups); - + std::cout << std::endl; - std::cout << "Factory calling information written to " << plugin_groups.size() << " plugin files plus overall summary." << std::endl; + std::cout << "Factory calling information written to " << plugin_groups.size() + << " plugin files plus overall summary." << std::endl; std::cout << "Plugin files use period-separated naming (e.g., jana.tracking.dot)." << std::endl; - std::cout << "Overall summary in \"" << output_filename << "\" shows inter-plugin connections." << std::endl; + std::cout << "Overall summary in \"" << output_filename << "\" shows inter-plugin connections." + << std::endl; std::cout << std::endl; } -void JEventProcessorJANADOT::WritePluginDotFile(const std::string& plugin_name, const std::set& nodes) { +void JEventProcessorJANADOT::WritePluginDotFile(const std::string& plugin_name, + const std::set& nodes) { // Create filename using period-separated plugin name std::string base_filename = output_filename; - size_t dot_pos = base_filename.find_last_of('.'); + size_t dot_pos = base_filename.find_last_of('.'); if (dot_pos != std::string::npos) { base_filename = base_filename.substr(0, dot_pos); } - + std::string filename = base_filename + "." + plugin_name + ".dot"; - + std::ofstream ofs(filename); if (!ofs.is_open()) { std::cerr << "Error: Unable to open file " << filename << " for writing!" << std::endl; @@ -684,7 +688,8 @@ void JEventProcessorJANADOT::WritePluginDotFile(const std::string& plugin_name, ofs.close(); } -void JEventProcessorJANADOT::WriteOverallDotFile(const std::map>& plugin_groups) { +void JEventProcessorJANADOT::WriteOverallDotFile( + const std::map>& plugin_groups) { std::ofstream ofs(output_filename); if (!ofs.is_open()) { std::cerr << "Error: Unable to open file " << output_filename << " for writing!" << std::endl; @@ -724,11 +729,11 @@ void JEventProcessorJANADOT::WriteOverallDotFile(const std::map plugin_times; std::map plugin_node_counts; - + for (auto& [plugin_name, nodes] : plugin_groups) { double plugin_time = 0.0; - int node_count = 0; - + int node_count = 0; + for (const std::string& nametag : nodes) { auto fstats_it = factory_stats.find(nametag); if (fstats_it != factory_stats.end()) { @@ -737,12 +742,12 @@ void JEventProcessorJANADOT::WriteOverallDotFile(const std::map, std::pair> inter_plugin_calls; - + for (auto& [link, stats] : call_links) { std::string caller = MakeNametag(link.caller_name, link.caller_tag); std::string callee = MakeNametag(link.callee_name, link.callee_tag); - + std::string caller_plugin = ExtractPluginName(caller); std::string callee_plugin = ExtractPluginName(callee); - + // Only show inter-plugin connections if (caller_plugin != callee_plugin && !caller_plugin.empty() && !callee_plugin.empty()) { auto key = std::make_pair(caller_plugin, callee_plugin); - + unsigned int total_calls = stats.Nfrom_cache + stats.Nfrom_source + stats.Nfrom_factory + stats.Ndata_not_available; double total_time = stats.from_cache_ms + stats.from_source_ms + stats.from_factory_ms + stats.data_not_available_ms; - + inter_plugin_calls[key].first += total_calls; inter_plugin_calls[key].second += total_time; } } - + // Write inter-plugin edges for (auto& [plugins, call_data] : inter_plugin_calls) { - int total_calls = call_data.first; + int total_calls = call_data.first; double total_time = call_data.second; - double percent = 100.0 * total_time / total_ms; - + double percent = 100.0 * total_time / total_ms; + ofs << " \"" << plugins.first << "\" -> \"" << plugins.second << "\" ["; ofs << "label=\"" << total_calls << " calls\\n"; ofs << MakeTimeString(total_time) << " (" << std::fixed << std::setprecision(1) << percent @@ -812,78 +817,89 @@ std::map> JEventProcessorJANADOT::SplitGraphB std::string JEventProcessorJANADOT::ExtractPluginName(const std::string& nametag) { // Try to extract meaningful plugin names from component names - + // Common EIC patterns if (nametag.find("Ecal") != std::string::npos) { - if (nametag.find("Barrel") != std::string::npos) return "ecal_barrel"; - if (nametag.find("Endcap") != std::string::npos) return "ecal_endcap"; - if (nametag.find("Insert") != std::string::npos) return "ecal_insert"; - if (nametag.find("LumiSpec") != std::string::npos) return "ecal_lumispec"; + if (nametag.find("Barrel") != std::string::npos) + return "ecal_barrel"; + if (nametag.find("Endcap") != std::string::npos) + return "ecal_endcap"; + if (nametag.find("Insert") != std::string::npos) + return "ecal_insert"; + if (nametag.find("LumiSpec") != std::string::npos) + return "ecal_lumispec"; return "ecal"; } - + if (nametag.find("Hcal") != std::string::npos) { - if (nametag.find("Barrel") != std::string::npos) return "hcal_barrel"; - if (nametag.find("Endcap") != std::string::npos) return "hcal_endcap"; - if (nametag.find("Insert") != std::string::npos) return "hcal_insert"; + if (nametag.find("Barrel") != std::string::npos) + return "hcal_barrel"; + if (nametag.find("Endcap") != std::string::npos) + return "hcal_endcap"; + if (nametag.find("Insert") != std::string::npos) + return "hcal_insert"; return "hcal"; } - + if (nametag.find("Track") != std::string::npos || nametag.find("track") != std::string::npos) { - if (nametag.find("CKF") != std::string::npos) return "tracking_ckf"; - if (nametag.find("Seed") != std::string::npos) return "tracking_seeding"; - if (nametag.find("Vertex") != std::string::npos) return "tracking_vertex"; + if (nametag.find("CKF") != std::string::npos) + return "tracking_ckf"; + if (nametag.find("Seed") != std::string::npos) + return "tracking_seeding"; + if (nametag.find("Vertex") != std::string::npos) + return "tracking_vertex"; return "tracking"; } - - if (nametag.find("Cluster") != std::string::npos || nametag.find("cluster") != std::string::npos) { + + if (nametag.find("Cluster") != std::string::npos || + nametag.find("cluster") != std::string::npos) { return "clustering"; } - + if (nametag.find("PID") != std::string::npos || nametag.find("Pid") != std::string::npos) { return "pid"; } - + if (nametag.find("Jet") != std::string::npos || nametag.find("jet") != std::string::npos) { return "jets"; } - + if (nametag.find("Truth") != std::string::npos || nametag.find("MC") != std::string::npos) { return "truth"; } - + if (nametag.find("B0") != std::string::npos || nametag.find("ZDC") != std::string::npos) { return "far_detectors"; } - + if (nametag.find("RICH") != std::string::npos) { return "rich"; } - + if (nametag.find("TOF") != std::string::npos) { return "tof"; } - + if (nametag.find("DIRC") != std::string::npos) { return "dirc"; } - + if (nametag.find("GEM") != std::string::npos || nametag.find("MPGD") != std::string::npos) { return "gem_tracking"; } - + if (nametag.find("Silicon") != std::string::npos || nametag.find("Pixel") != std::string::npos) { return "silicon_tracking"; } - + if (nametag.find("Processor") != std::string::npos) { return "processors"; } - + if (nametag.find("Source") != std::string::npos) { return "io"; } - + // Default fallback - try to use first part of name size_t colon_pos = nametag.find(':'); if (colon_pos != std::string::npos) { @@ -892,7 +908,7 @@ std::string JEventProcessorJANADOT::ExtractPluginName(const std::string& nametag std::transform(base_name.begin(), base_name.end(), base_name.begin(), ::tolower); return base_name; } - + // Final fallback return "misc"; } From d5a815fa858fe80d775679a8448b32b11bc6a3c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 14:32:02 +0000 Subject: [PATCH 06/29] Fix janadot plugin parameter conflict by renaming to eicjanadot Co-authored-by: wdconinc <4656391+wdconinc@users.noreply.github.com> --- .github/workflows/linux-eic-shell.yml | 10 +-- src/utilities/CMakeLists.txt | 2 +- .../{janadot => eicjanadot}/CMakeLists.txt | 0 .../JEventProcessorJANADOT.cc | 12 +-- .../JEventProcessorJANADOT.h | 0 src/utilities/eicjanadot/README.md | 80 +++++++++++++++++++ .../janadot.cc => eicjanadot/eicjanadot.cc} | 0 src/utilities/janadot/README.md | 57 ------------- 8 files changed, 92 insertions(+), 69 deletions(-) rename src/utilities/{janadot => eicjanadot}/CMakeLists.txt (100%) rename src/utilities/{janadot => eicjanadot}/JEventProcessorJANADOT.cc (98%) rename src/utilities/{janadot => eicjanadot}/JEventProcessorJANADOT.h (100%) create mode 100644 src/utilities/eicjanadot/README.md rename src/utilities/{janadot/janadot.cc => eicjanadot/eicjanadot.cc} (100%) delete mode 100644 src/utilities/janadot/README.md diff --git a/.github/workflows/linux-eic-shell.yml b/.github/workflows/linux-eic-shell.yml index dc92cc2a46..baaf930c0b 100644 --- a/.github/workflows/linux-eic-shell.yml +++ b/.github/workflows/linux-eic-shell.yml @@ -549,7 +549,7 @@ jobs: export DETECTOR_CONFIG=${DETECTOR}_${{ matrix.detector_config }} export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins${JANA_PLUGIN_PATH:+:${JANA_PLUGIN_PATH}} - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_collections=EventHeader,MCParticles,EcalBarrelScFiRawHits,EcalBarrelImagingRawHits -Ppodio:output_file=two_stage_raw_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,janadot -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_collections=EventHeader,MCParticles,EcalBarrelScFiRawHits,EcalBarrelImagingRawHits -Ppodio:output_file=two_stage_raw_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,eicjanadot -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json - name: Upload digitization output uses: actions/upload-artifact@v4 with: @@ -565,7 +565,7 @@ jobs: export DETECTOR_CONFIG=${DETECTOR}_${{ matrix.detector_config }} export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins${JANA_PLUGIN_PATH:+:${JANA_PLUGIN_PATH}} - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_collections=EcalBarrelClusters,EcalBarrelClusterAssociations -Ppodio:output_file=two_stage_rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root two_stage_raw_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root -Pplugins=dump_flags,janadot -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_collections=EcalBarrelClusters,EcalBarrelClusterAssociations -Ppodio:output_file=two_stage_rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root two_stage_raw_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root -Pplugins=dump_flags,eicjanadot -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json - name: Upload reconstruction output uses: actions/upload-artifact@v4 with: @@ -709,7 +709,7 @@ jobs: export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins prmon --json-summary rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.prmon.json -- \ - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,janadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Pjanadot:split_criteria=plugin + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,eicjanadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Peicjanadot:split_criteria=plugin - uses: actions/upload-artifact@v4 with: name: rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root @@ -817,7 +817,7 @@ jobs: export DETECTOR_CONFIG=${DETECTOR}_${{ matrix.detector_config }} export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4hep.root -Ppodio:output_collections=EcalLumiSpecRawHits,EcalLumiSpecRecHits,EcalLumiSpecClusters,EcalLumiSpecClusterAssociations -PLUMISPECCAL:EcalLumiSpecIslandProtoClusters:splitCluster=1 -Pplugins=dump_flags,janadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Pjanadot:split_criteria=plugin + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4hep.root -Ppodio:output_collections=EcalLumiSpecRawHits,EcalLumiSpecRecHits,EcalLumiSpecClusters,EcalLumiSpecClusterAssociations -PLUMISPECCAL:EcalLumiSpecIslandProtoClusters:splitCluster=1 -Pplugins=dump_flags,eicjanadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Peicjanadot:split_criteria=plugin - uses: actions/upload-artifact@v4 with: name: rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root @@ -929,7 +929,7 @@ jobs: export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins prmon --json-summary rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.prmon.json -- \ - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{matrix.nthreads > 1 && format('-Pnthreads={0}', matrix.nthreads) || ''}} -Ppodio:output_file=rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root sim_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}.edm4hep.root -Pacts:WriteObj=true -Pacts:WritePly=true -Pplugins=janadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Pjanadot:split_criteria=plugin + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{matrix.nthreads > 1 && format('-Pnthreads={0}', matrix.nthreads) || ''}} -Ppodio:output_file=rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root sim_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}.edm4hep.root -Pacts:WriteObj=true -Pacts:WritePly=true -Pplugins=eicjanadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Peicjanadot:split_criteria=plugin - uses: actions/upload-artifact@v4 with: name: rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root diff --git a/src/utilities/CMakeLists.txt b/src/utilities/CMakeLists.txt index 7af312d2aa..193be84963 100644 --- a/src/utilities/CMakeLists.txt +++ b/src/utilities/CMakeLists.txt @@ -1,4 +1,4 @@ add_subdirectory(dump_flags) add_subdirectory(eicrecon) -add_subdirectory(janadot) +add_subdirectory(eicjanadot) add_subdirectory(janatop) diff --git a/src/utilities/janadot/CMakeLists.txt b/src/utilities/eicjanadot/CMakeLists.txt similarity index 100% rename from src/utilities/janadot/CMakeLists.txt rename to src/utilities/eicjanadot/CMakeLists.txt diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/eicjanadot/JEventProcessorJANADOT.cc similarity index 98% rename from src/utilities/janadot/JEventProcessorJANADOT.cc rename to src/utilities/eicjanadot/JEventProcessorJANADOT.cc index 09f911a50d..bba9494cf5 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/eicjanadot/JEventProcessorJANADOT.cc @@ -23,22 +23,22 @@ void JEventProcessorJANADOT::Init() { // Set default parameter values and register them output_filename = "jana.dot"; - params->SetDefaultParameter("janadot:output_file", output_filename, "Output DOT filename"); + params->SetDefaultParameter("eicjanadot:output_file", output_filename, "Output DOT filename"); enable_splitting = true; - params->SetDefaultParameter("janadot:enable_splitting", enable_splitting, + params->SetDefaultParameter("eicjanadot:enable_splitting", enable_splitting, "Enable splitting large graphs into multiple files"); max_nodes_per_graph = 50; - params->SetDefaultParameter("janadot:max_nodes_per_graph", max_nodes_per_graph, + params->SetDefaultParameter("eicjanadot:max_nodes_per_graph", max_nodes_per_graph, "Maximum number of nodes per graph when splitting"); max_edges_per_graph = 100; - params->SetDefaultParameter("janadot:max_edges_per_graph", max_edges_per_graph, + params->SetDefaultParameter("eicjanadot:max_edges_per_graph", max_edges_per_graph, "Maximum number of edges per graph when splitting"); - split_criteria = "size"; - params->SetDefaultParameter("janadot:split_criteria", split_criteria, + split_criteria = "plugin"; + params->SetDefaultParameter("eicjanadot:split_criteria", split_criteria, "Criteria for splitting graphs: size, components, type, plugin"); } diff --git a/src/utilities/janadot/JEventProcessorJANADOT.h b/src/utilities/eicjanadot/JEventProcessorJANADOT.h similarity index 100% rename from src/utilities/janadot/JEventProcessorJANADOT.h rename to src/utilities/eicjanadot/JEventProcessorJANADOT.h diff --git a/src/utilities/eicjanadot/README.md b/src/utilities/eicjanadot/README.md new file mode 100644 index 0000000000..5fc12dc675 --- /dev/null +++ b/src/utilities/eicjanadot/README.md @@ -0,0 +1,80 @@ +# EIC JANADOT Plugin + +This plugin creates DOT (graphviz) files from JANA2 call graphs for visualization purposes. + +## Overview + +The eicjanadot plugin records call stack information during event processing and generates DOT format files that can be processed by graphviz to create visual call graphs. It includes functionality to split large graphs into multiple smaller graphs for better processing and readability. + +## Usage + +To use the plugin, add it to your eicrecon command: + +```bash +eicrecon -Pplugins=eicjanadot sim_file.edm4hep.root +``` + +## Configuration Parameters + +The plugin supports several configuration parameters: + +- `eicjanadot:output_file` (default: "jana.dot") - Output DOT filename +- `eicjanadot:enable_splitting` (default: true) - Enable splitting large graphs into multiple files +- `eicjanadot:max_nodes_per_graph` (default: 50) - Maximum number of nodes per graph when splitting +- `eicjanadot:max_edges_per_graph` (default: 100) - Maximum number of edges per graph when splitting +- `eicjanadot:split_criteria` (default: "plugin") - Criteria for splitting graphs: size, components, type, plugin + +## Plugin-based Splitting + +The default splitting method groups components by detector subsystem plugins: + +```bash +eicrecon -Pplugins=eicjanadot -Peicjanadot:split_criteria=plugin sim_file.root +``` + +This generates: +- `jana.tracking.dot` - All tracking-related components +- `jana.ecal_barrel.dot` - ECAL barrel subsystem components +- `jana.hcal_endcap.dot` - HCAL endcap subsystem components +- `jana.dot` - Overall inter-plugin connection summary + +## Output Files + +When splitting is disabled or graphs are small enough, a single DOT file is generated. When splitting is enabled and graphs are large, multiple files are created: + +- `jana_part001.dot`, `jana_part002.dot`, etc. - Individual graph parts (for size/components/type splitting) +- `jana.plugin_name.dot` - Plugin-specific graphs (for plugin splitting) +- `jana.dot` - Main graph showing inter-plugin connections (for plugin splitting) +- `jana_index.txt` - Index file explaining the split and how to process the files + +## Generating Graphics + +To convert DOT files to graphics: + +```bash +# For a single file +dot -Tpdf jana.dot -o jana.pdf + +# For split files (as shown in the index file) +dot -Tpdf jana_part001.dot -o jana_part001.pdf +dot -Tpdf jana_part002.dot -o jana_part002.pdf +# ... etc + +# For plugin-based splits +dot -Tpdf jana.tracking.dot -o jana.tracking.pdf +dot -Tpdf jana.ecal_barrel.dot -o jana.ecal_barrel.pdf +``` + +## Graph Splitting Methods + +### Plugin-based Splitting (default) +Groups components by detector subsystem, providing both detailed subsystem views and high-level architectural overview. + +### Size-based Splitting +Nodes are grouped to keep within the specified limits of nodes and edges per graph. + +### Component-based Splitting +Uses connected components analysis to group nodes that are connected by call relationships. + +### Type-based Splitting +Groups nodes by their type (Processor, Factory, Source, etc.). diff --git a/src/utilities/janadot/janadot.cc b/src/utilities/eicjanadot/eicjanadot.cc similarity index 100% rename from src/utilities/janadot/janadot.cc rename to src/utilities/eicjanadot/eicjanadot.cc diff --git a/src/utilities/janadot/README.md b/src/utilities/janadot/README.md deleted file mode 100644 index 3306fb5953..0000000000 --- a/src/utilities/janadot/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# JANADOT Plugin - -This plugin creates DOT (graphviz) files from JANA2 call graphs for visualization purposes. - -## Overview - -The janadot plugin records call stack information during event processing and generates DOT format files that can be processed by graphviz to create visual call graphs. It includes functionality to split large graphs into multiple smaller graphs for better processing and readability. - -## Usage - -To use the plugin, add it to your eicrecon command: - -```bash -eicrecon -Pplugins=janadot sim_file.edm4hep.root -``` - -## Configuration Parameters - -The plugin supports several configuration parameters: - -- `janadot:output_file` (default: "jana.dot") - Output DOT filename -- `janadot:enable_splitting` (default: true) - Enable splitting large graphs into multiple files -- `janadot:max_nodes_per_graph` (default: 50) - Maximum number of nodes per graph when splitting -- `janadot:max_edges_per_graph` (default: 100) - Maximum number of edges per graph when splitting -- `janadot:split_criteria` (default: "size") - Criteria for splitting graphs: size, components, type - -## Output Files - -When splitting is disabled or graphs are small enough, a single DOT file is generated. When splitting is enabled and graphs are large, multiple files are created: - -- `jana_part001.dot`, `jana_part002.dot`, etc. - Individual graph parts -- `jana_index.txt` - Index file explaining the split and how to process the files - -## Generating Graphics - -To convert DOT files to graphics: - -```bash -# For a single file -dot -Tpdf jana.dot -o jana.pdf - -# For split files (as shown in the index file) -dot -Tpdf jana_part001.dot -o jana_part001.pdf -dot -Tpdf jana_part002.dot -o jana_part002.pdf -# ... etc -``` - -## Graph Splitting Methods - -### Size-based Splitting -Nodes are grouped to keep within the specified limits of nodes and edges per graph. - -### Component-based Splitting -Uses connected components analysis to group nodes that are connected by call relationships. - -### Type-based Splitting -Groups nodes by their type (Processor, Factory, Source, etc.). From 7ba3a281f13d575c235724a83821e21856fcca03 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Aug 2025 14:40:30 +0000 Subject: [PATCH 07/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/utilities/eicjanadot/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities/eicjanadot/README.md b/src/utilities/eicjanadot/README.md index 5fc12dc675..b90571079e 100644 --- a/src/utilities/eicjanadot/README.md +++ b/src/utilities/eicjanadot/README.md @@ -35,7 +35,7 @@ eicrecon -Pplugins=eicjanadot -Peicjanadot:split_criteria=plugin sim_file.root This generates: - `jana.tracking.dot` - All tracking-related components - `jana.ecal_barrel.dot` - ECAL barrel subsystem components -- `jana.hcal_endcap.dot` - HCAL endcap subsystem components +- `jana.hcal_endcap.dot` - HCAL endcap subsystem components - `jana.dot` - Overall inter-plugin connection summary ## Output Files From eb8fa258fab4affb401fe2850afd4e67fe45d10f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 14:58:32 +0000 Subject: [PATCH 08/29] Remove old janadot group parameters from CI workflow to fix parameter errors Co-authored-by: wdconinc <4656391+wdconinc@users.noreply.github.com> --- .github/workflows/linux-eic-shell.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux-eic-shell.yml b/.github/workflows/linux-eic-shell.yml index baaf930c0b..dc09d0b983 100644 --- a/.github/workflows/linux-eic-shell.yml +++ b/.github/workflows/linux-eic-shell.yml @@ -709,7 +709,7 @@ jobs: export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins prmon --json-summary rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.prmon.json -- \ - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,eicjanadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Peicjanadot:split_criteria=plugin + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,eicjanadot,janatop -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Peicjanadot:split_criteria=plugin - uses: actions/upload-artifact@v4 with: name: rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root @@ -817,7 +817,7 @@ jobs: export DETECTOR_CONFIG=${DETECTOR}_${{ matrix.detector_config }} export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4hep.root -Ppodio:output_collections=EcalLumiSpecRawHits,EcalLumiSpecRecHits,EcalLumiSpecClusters,EcalLumiSpecClusterAssociations -PLUMISPECCAL:EcalLumiSpecIslandProtoClusters:splitCluster=1 -Pplugins=dump_flags,eicjanadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Peicjanadot:split_criteria=plugin + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4hep.root -Ppodio:output_collections=EcalLumiSpecRawHits,EcalLumiSpecRecHits,EcalLumiSpecClusters,EcalLumiSpecClusterAssociations -PLUMISPECCAL:EcalLumiSpecIslandProtoClusters:splitCluster=1 -Pplugins=dump_flags,eicjanadot,janatop -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Peicjanadot:split_criteria=plugin - uses: actions/upload-artifact@v4 with: name: rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root @@ -929,7 +929,7 @@ jobs: export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins prmon --json-summary rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.prmon.json -- \ - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{matrix.nthreads > 1 && format('-Pnthreads={0}', matrix.nthreads) || ''}} -Ppodio:output_file=rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root sim_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}.edm4hep.root -Pacts:WriteObj=true -Pacts:WritePly=true -Pplugins=eicjanadot,janatop $(<${{ github.workspace }}/.github/janadot.groups) -Peicjanadot:split_criteria=plugin + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{matrix.nthreads > 1 && format('-Pnthreads={0}', matrix.nthreads) || ''}} -Ppodio:output_file=rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root sim_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}.edm4hep.root -Pacts:WriteObj=true -Pacts:WritePly=true -Pplugins=eicjanadot,janatop -Peicjanadot:split_criteria=plugin - uses: actions/upload-artifact@v4 with: name: rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root From 67e014c536e01d17daa45510f6c389bde3516b5b Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 23 Aug 2025 11:19:13 -0500 Subject: [PATCH 09/29] Add eicjanadot plugin with enhanced plugin-based graph splitting and inter-plugin visualization (fix: iwyu) (#2041) This PR applies the include-what-you-use fixes as suggested by https://github.com/eic/EICrecon/actions/runs/17176834842. Please merge this PR into the branch `copilot/fix-fa2a3ce4-8a21-441e-9641-77391e5b455f` to resolve failures in PR #2040. Auto-generated by [create-pull-request][1] [1]: https://github.com/peter-evans/create-pull-request Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../eicjanadot/JEventProcessorJANADOT.cc | 17 ++++++++++------- .../eicjanadot/JEventProcessorJANADOT.h | 7 +++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/utilities/eicjanadot/JEventProcessorJANADOT.cc b/src/utilities/eicjanadot/JEventProcessorJANADOT.cc index bba9494cf5..2974ebff03 100644 --- a/src/utilities/eicjanadot/JEventProcessorJANADOT.cc +++ b/src/utilities/eicjanadot/JEventProcessorJANADOT.cc @@ -4,18 +4,21 @@ #include "JEventProcessorJANADOT.h" #include -#include #include +#include #include - -#include -#include -#include -#include +#include +#include #include -#include #include +#include +#include #include +#include +#include +#include +#include +#include void JEventProcessorJANADOT::Init() { // Get parameter manager diff --git a/src/utilities/eicjanadot/JEventProcessorJANADOT.h b/src/utilities/eicjanadot/JEventProcessorJANADOT.h index 8b6a6f6845..fba2b4ff55 100644 --- a/src/utilities/eicjanadot/JEventProcessorJANADOT.h +++ b/src/utilities/eicjanadot/JEventProcessorJANADOT.h @@ -4,12 +4,11 @@ #pragma once #include -#include - +#include #include -#include -#include +#include #include +#include #include class JEventProcessorJANADOT : public JEventProcessor { From 62f5397209fe1b515e7889b1b6b47626cfb4d222 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:26:06 +0000 Subject: [PATCH 10/29] Revert plugin name back to janadot and implement major code refactoring Co-authored-by: wdconinc <4656391+wdconinc@users.noreply.github.com> --- .github/janadot.groups | 42 ------ .github/workflows/linux-eic-shell.yml | 17 ++- docs/howto/visualize_callgraph.md | 19 ++- src/utilities/CMakeLists.txt | 2 +- .../{eicjanadot => janadot}/CMakeLists.txt | 0 .../JEventProcessorJANADOT.cc | 127 ++++++++++++++---- .../JEventProcessorJANADOT.h | 3 +- .../{eicjanadot => janadot}/README.md | 18 +-- .../eicjanadot.cc => janadot/janadot.cc} | 0 9 files changed, 145 insertions(+), 83 deletions(-) delete mode 100644 .github/janadot.groups rename src/utilities/{eicjanadot => janadot}/CMakeLists.txt (100%) rename src/utilities/{eicjanadot => janadot}/JEventProcessorJANADOT.cc (88%) rename src/utilities/{eicjanadot => janadot}/JEventProcessorJANADOT.h (95%) rename src/utilities/{eicjanadot => janadot}/README.md (69%) rename src/utilities/{eicjanadot/eicjanadot.cc => janadot/janadot.cc} (100%) diff --git a/.github/janadot.groups b/.github/janadot.groups deleted file mode 100644 index 4e09795512..0000000000 --- a/.github/janadot.groups +++ /dev/null @@ -1,42 +0,0 @@ --PJANADOT:GROUP:EcalEndcapN=edm4hep::SimCalorimeterHit:EcalEndcapNHits,edm4hep::RawCalorimeterHit:EcalEndcapNRawHits,edm4eic::CalorimeterHit:EcalEndcapNRecHits,edm4eic::ProtoCluster:EcalEndcapNIslandProtoClusters,edm4eic::Cluster:EcalEndcapNClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapNClusterAssociations,edm4eic::ProtoCluster:EcalEndcapNTruthProtoClusters,edm4eic::Cluster:EcalEndcapNTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapNTruthClusterAssociations,color_blue --PJANADOT:GROUP:EcalBarrelScFi=edm4hep::SimCalorimeterHit:EcalBarrelScFiHits,edm4hep::RawCalorimeterHit:EcalBarrelScFiRawHits,edm4eic::CalorimeterHit:EcalBarrelScFiRecHits,edm4eic::ProtoCluster:EcalBarrelScFiProtoClusters,edm4eic::Cluster:EcalBarrelScFiClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelScFiClusterAssociations,color_red --PJANADOT:GROUP:EcalBarrelImaging=edm4hep::SimCalorimeterHit:EcalBarrelImagingHits,edm4hep::RawCalorimeterHit:EcalBarrelImagingRawHits,edm4eic::CalorimeterHit:EcalBarrelImagingRecHits,edm4eic::ProtoCluster:EcalBarrelImagingProtoClusters,edm4eic::Cluster:EcalBarrelImagingClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelImagingClusterAssociations,color_blue --PJANADOT:GROUP:EcalBarrel=edm4eic::Cluster:EcalBarrelClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelClusterAssociations,edm4eic::Cluster:EcalBarrelTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelTruthClusterAssociations,color_blue --PJANADOT:GROUP:Ecal=edm4eic::Cluster:EcalClusters,edm4eic::MCRecoClusterParticleAssociation:EcalClusterAssociations,color_blue --PJANADOT:GROUP:EcalEndcapP=edm4hep::SimCalorimeterHit:EcalEndcapPHits,edm4hep::RawCalorimeterHit:EcalEndcapPRawHits,edm4eic::CalorimeterHit:EcalEndcapPRecHits,edm4eic::ProtoCluster:EcalEndcapPIslandProtoClusters,edm4eic::Cluster:EcalEndcapPClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapPClusterAssociations,edm4eic::ProtoCluster:EcalEndcapPTruthProtoClusters,edm4eic::Cluster:EcalEndcapPTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapPTruthClusterAssociations,color_blue --PJANADOT:GROUP:B0Ecal=edm4hep::SimCalorimeterHit:B0ECalHits,edm4hep::RawCalorimeterHit:B0ECalRawHits,edm4eic::CalorimeterHit:B0ECalRecHits,edm4eic::ProtoCluster:B0ECalIslandProtoClusters,edm4eic::Cluster:B0ECalClusters,edm4eic::MCRecoClusterParticleAssociation:B0ECalClusterAssociations,color_blue --PJANADOT:GROUP:EcalFarForwardZDC=edm4hep::SimCalorimeterHit:EcalFarForwardZDCHits,edm4hep::RawCalorimeterHit:EcalFarForwardZDCRawHits,edm4eic::CalorimeterHit:EcalFarForwardZDCRecHits,edm4eic::CalorimeterHit:EcalFarForwardZDCSubcellHits,edm4eic::ProtoCluster:EcalFarForwardZDCImagingProtoClusters,edm4eic::ProtoCluster:EcalFarForwardZDCIslandProtoClusters,edm4eic::Cluster:EcalFarForwardZDCClusters,edm4eic::MCRecoClusterParticleAssociation:EcalFarForwardZDCClusterAssociations,edm4eic::ProtoCluster:EcalFarForwardZDCTruthProtoClusters,edm4eic::Cluster:EcalFarForwardZDCTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalFarForwardZDCTruthClusterAssociations,color_red --PJANADOT:GROUP:EcalLumiSpec=edm4hep::SimCalorimeterHit:EcalLumiSpecHits,edm4hep::RawCalorimeterHit:EcalLumiSpecRawHits,edm4eic::CalorimeterHit:EcalLumiSpecRecHits,edm4eic::CalorimeterHit:EcalLumiSpecSubcellHits,edm4eic::ProtoCluster:EcalLumiSpecImagingProtoClusters,edm4eic::ProtoCluster:EcalLumiSpecIslandProtoClusters,edm4eic::Cluster:EcalLumiSpecClusters,edm4eic::MCRecoClusterParticleAssociation:EcalLumiSpecClusterAssociations,edm4eic::ProtoCluster:EcalLumiSpecTruthProtoClusters,edm4eic::Cluster:EcalLumiSpecTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalLumiSpecTruthClusterAssociations,color_red - --PJANADOT:GROUP:HcalEndcapN=edm4hep::SimCalorimeterHit:HcalEndcapNHits,edm4hep::RawCalorimeterHit:HcalEndcapNRawHits,edm4eic::CalorimeterHit:HcalEndcapNRecHits,edm4eic::CalorimeterHit:HcalEndcapNMergedHits,edm4eic::ProtoCluster:HcalEndcapNIslandProtoClusters,edm4eic::Cluster:HcalEndcapNClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapNClusterAssociations,edm4eic::ProtoCluster:HcalEndcapNTruthProtoClusters,edm4eic::Cluster:HcalEndcapNTruthClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapNTruthClusterAssociations,color_red --PJANADOT:GROUP:HcalBarrel=edm4hep::SimCalorimeterHit:HcalBarrelHits,edm4hep::RawCalorimeterHit:HcalBarrelRawHits,edm4eic::CalorimeterHit:HcalBarrelRecHits,edm4eic::ProtoCluster:HcalBarrelIslandProtoClusters,edm4eic::Cluster:HcalBarrelClusters,edm4eic::MCRecoClusterParticleAssociation:HcalBarrelClusterAssociations,edm4eic::ProtoCluster:HcalBarrelTruthProtoClusters,edm4eic::Cluster:HcalBarrelTruthClusters,edm4eic::MCRecoClusterParticleAssociation:HcalBarrelTruthClusterAssociations,color_red --PJANADOT:GROUP:HcalEndcapP=edm4hep::SimCalorimeterHit:HcalEndcapPHits,edm4hep::RawCalorimeterHit:HcalEndcapPRawHits,edm4eic::CalorimeterHit:HcalEndcapPRecHits,edm4eic::ProtoCluster:HcalEndcapPIslandProtoClusters,edm4eic::Cluster:HcalEndcapPClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapPClusterAssociations,color_red --PJANADOT:GROUP:LFHCAL=edm4hep::SimCalorimeterHit:LFHCALHits,edm4hep::RawCalorimeterHit:LFHCALRawHits,edm4eic::CalorimeterHit:LFHCALRecHits,edm4eic::ProtoCluster:LFHCALIslandProtoClusters,edm4eic::Cluster:LFHCALClusters,edm4eic::MCRecoClusterParticleAssociation:LFHCALClusterAssociations,color_red --PJANADOT:GROUP:HcalEndcapPInsert=edm4hep::SimCalorimeterHit:HcalEndcapPInsertHits,edm4hep::RawCalorimeterHit:HcalEndcapPInsertRawHits,edm4eic::CalorimeterHit:HcalEndcapPInsertRecHits,edm4eic::CalorimeterHit:HcalEndcapPInsertMergedHits,edm4eic::ProtoCluster:HcalEndcapPInsertIslandProtoClusters,edm4eic::Cluster:HcalEndcapPInsertClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapPInsertClusterAssociations,color_red --PJANADOT:GROUP:HcalFarForwardZDC=edm4hep::SimCalorimeterHit:HcalFarForwardZDCHits,edm4hep::RawCalorimeterHit:HcalFarForwardZDCRawHits,edm4eic::CalorimeterHit:HcalFarForwardZDCRecHits,edm4eic::CalorimeterHit:HcalFarForwardZDCSubcellHits,edm4eic::ProtoCluster:HcalFarForwardZDCImagingProtoClusters,edm4eic::ProtoCluster:HcalFarForwardZDCIslandProtoClusters,edm4eic::Cluster:HcalFarForwardZDCClusters,edm4eic::MCRecoClusterParticleAssociation:HcalFarForwardZDCClusterAssociations,edm4eic::ProtoCluster:HcalFarForwardZDCTruthProtoClusters,edm4eic::Cluster:HcalFarForwardZDCTruthClusters,edm4eic::MCRecoClusterParticleAssociation:HcalFarForwardZDCTruthClusterAssociations,edm4eic::ProtoCluster:HcalFarForwardZDCIslandProtoClustersBaseline,edm4eic::Cluster:HcalFarForwardZDCClustersBaseline,edm4eic::MCRecoClusterParticleAssociation:HcalFarForwardZDCClusterAssociationsBaseline,color_red --PJANADOT:GROUP:RICHEndcapN=edm4hep::SimTrackerHit:RICHEndcapNHits,edm4eic::RawTrackerHit:RICHEndcapNRawHits,edm4eic::MCRecoTrackerHitAssociation:RICHEndcapNRawHitsAssociations,color_green --PJANADOT:GROUP:DIRC=edm4hep::SimTrackerHit:DIRCBarHits,edm4eic::RawTrackerHit:DIRCRawHits,color_green - --PJANADOT:GROUP:Tracking=edm4eic::TrackerHit:CentralTrackingRecHits,edm4eic::TrackParameters:CentralTrackSeedingResults,edm4eic::Measurement2D:CentralTrackerMeasurements,edm4eic::TrackParameters:CentralCKFSeededTrackParameters,ActsExamples::Trajectories:CentralCKFActsTrajectories,Acts::TrackContainer:CentralCKFActsTracks,edm4eic::TrackParameters:CentralCKFTrackParameters,edm4eic::Track:CentralCKFTracks - --PJANADOT:GROUP:ForwardRomanPot=edm4hep::SimTrackerHit:ForwardRomanPotHits,edm4eic::RawTrackerHit:ForwardRomanPotRawHits,edm4eic::TrackerHit:ForwardRomanPotRecHits,edm4eic::MCRecoTrackerHitAssociation:ForwardRomanPotHitAssociations,edm4eic::ReconstructedParticle:ForwardRomanPotRecParticles,color_blue --PJANADOT:GROUP:ForwardOffMTracker=edm4eic::RawTrackerHit:ForwardOffMTrackerRawHits,edm4eic::TrackerHit:ForwardOffMTrackerRecHits,edm4eic::ReconstructedParticle:ForwardOffMRecParticles,color_blue - --PJANADOT:GROUP:B0Tracker=edm4hep::SimTrackerHit:B0TrackerHits,edm4eic::RawTrackerHit:B0TrackerRawHits,edm4eic::TrackerHit:B0TrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:B0TrackerHitAssociations,color_blue --PJANADOT:GROUP:BackwardMPGDEndcap=edm4hep::SimTrackerHit:BackwardMPGDEndcapHits,edm4eic::RawTrackerHit:BackwardMPGDEndcapRawHits,edm4eic::TrackerHit:BackwardMPGDEndcapRecHits,edm4eic::MCRecoTrackerHitAssociation:BackwardMPGDEndcapAssociations,color_blue --PJANADOT:GROUP:MPGDBarrel=edm4hep::SimTrackerHit:MPGDBarrelHits,edm4eic::RawTrackerHit:MPGDBarrelRawHits,edm4eic::TrackerHit:MPGDBarrelRecHits,edm4eic::MCRecoTrackerHitAssociation:MPGDBarrelHitAssociations,color_blue --PJANADOT:GROUP:OuterMPGDBarrel=edm4hep::SimTrackerHit:OuterMPGDBarrelHits,edm4eic::RawTrackerHit:OuterMPGDBarrelRawHits,edm4eic::TrackerHit:OuterMPGDBarrelRecHits,edm4eic::MCRecoTrackerHitAssociation:OuterMPGDBarrelHitAssociations,color_blue --PJANADOT:GROUP:ForwardMPGDEndcap=edm4hep::SimTrackerHit:ForwardMPGDEndcapHits,edm4eic::RawTrackerHit:ForwardMPGDEndcapRawHits,edm4eic::TrackerHit:ForwardMPGDEndcapRecHits,edm4eic::MCRecoTrackerHitAssociation:ForwardMPGDHitAssociations,color_blue --PJANADOT:GROUP:Tagger=edm4hep::SimTrackerHit:TaggerTrackerHits,edm4eic::MCRecoTrackerHitAssociation:TaggerTrackerHitAssociations,edm4eic::RawTrackerHit:TaggerTrackerRawHits,edm4eic::RawTrackerHit:TaggerTrackerM1L0RawHits,edm4eic::RawTrackerHit:TaggerTrackerM1L1RawHits,edm4eic::RawTrackerHit:TaggerTrackerM1L2RawHits,edm4eic::RawTrackerHit:TaggerTrackerM1L3RawHits,edm4eic::RawTrackerHit:TaggerTrackerM2L0RawHits,edm4eic::RawTrackerHit:TaggerTrackerM2L1RawHits,edm4eic::RawTrackerHit:TaggerTrackerM2L2RawHits,edm4eic::RawTrackerHit:TaggerTrackerM2L3RawHits,edm4hep::TrackerHit:TaggerTrackerM1L0ClusterPositions,edm4hep::TrackerHit:TaggerTrackerM1L1ClusterPositions,edm4hep::TrackerHit:TaggerTrackerM1L2ClusterPositions,edm4hep::TrackerHit:TaggerTrackerM1L3ClusterPositions,edm4hep::TrackerHit:TaggerTrackerM2L0ClusterPositions,edm4hep::TrackerHit:TaggerTrackerM2L1ClusterPositions,edm4hep::TrackerHit:TaggerTrackerM2L2ClusterPositions,edm4hep::TrackerHit:TaggerTrackerM2L3ClusterPositions,edm4eic::TrackSegment:TaggerTrackerM1Tracks,edm4eic::TrackSegment:TaggerTrackerM2Tracks,edm4eic::TrackSegment:TaggerTrackerTracks,edm4eic::TrackParameters:TaggerTrackerProjectedTracks - --PJANADOT:GROUP:TOFBarrel=edm4hep::SimTrackerHit:TOFBarrelHits,edm4eic::RawTrackerHit:TOFBarrelRawHit,edm4eic::TrackerHit:TOFBarrelRecHit,edm4eic::MCRecoTrackerHitAssociation:TOFBarrelHitAssociations,color_blue --PJANADOT:GROUP:TOFEndcap=edm4hep::SimTrackerHit:TOFEndcapHits,edm4eic::RawTrackerHit:TOFEndcapRawHits,edm4eic::TrackerHit:TOFEndcapRecHits,edm4eic::MCRecoTrackerHitAssociation:TOFEndcapHitAssociations,color_blue - --PJANADOT:GROUP:SiBarrel=edm4hep::SimTrackerHit:SiBarrelHits,edm4eic::RawTrackerHit:SiBarrelRawHits,edm4eic::TrackerHit:SiBarrelTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:SiBarrelHitAssociations,color_blue --PJANADOT:GROUP:SiEndcap=edm4hep::SimTrackerHit:SiEndcapHits,edm4eic::RawTrackerHit:SiEndcapTrackerRawHits,edm4eic::TrackerHit:SiEndcapTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:SiEndcapHitAssociations,color_blue - --PJANADOT:GROUP:SiBarrelVertex=edm4hep::SimTrackerHit:SiBarrelVertexHits,edm4eic::RawTrackerHit:SiBarrelVertexRawHits,edm4eic::TrackerHit:SiBarrelVertexRecHits,edm4eic::MCRecoTrackerHitAssociation:SiBarrelVertexHitAssociations,color_blue - --PJANADOT:GROUP:Kinematics=edm4eic::InclusiveKinematics:InclusiveKinematicsTruth,edm4eic::InclusiveKinematics:InclusiveKinematicsElectron,edm4eic::InclusiveKinematics:InclusiveKinematicsSigma,edm4eic::InclusiveKinematics:InclusiveKinematicseSigma,edm4eic::InclusiveKinematics:InclusiveKinematicsJB,edm4eic::InclusiveKinematics:InclusiveKinematicsDA - --PJANADOT:GROUP:Truth=edm4eic::ReconstructedParticle:GeneratedParticles,edm4eic::ReconstructedParticle:GeneratedChargedParticles,edm4eic::ReconstructedParticle:GeneratedJets,edm4eic::ReconstructedParticle:GeneratedChargedJets diff --git a/.github/workflows/linux-eic-shell.yml b/.github/workflows/linux-eic-shell.yml index dc09d0b983..202115a94c 100644 --- a/.github/workflows/linux-eic-shell.yml +++ b/.github/workflows/linux-eic-shell.yml @@ -709,7 +709,7 @@ jobs: export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins prmon --json-summary rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.prmon.json -- \ - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,eicjanadot,janatop -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Peicjanadot:split_criteria=plugin + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,janadot,janatop -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Pjanadot:split_criteria=plugin - uses: actions/upload-artifact@v4 with: name: rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root @@ -726,10 +726,17 @@ jobs: platform-release: "${{ env.platform }}:${{ env.release }}" setup: "/opt/detector/epic-${{ env.detector-version }}/bin/thisepic.sh" run: | - # Rename main file and convert all dot files to SVG - if [ -f jana.dot ]; then - mv jana.dot rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.dot - fi + # Rename all jana dot files with matrix test name prefix + for dotfile in jana*.dot; do + if [ -f "$dotfile" ]; then + newname="rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.${dotfile#jana.}" + # Handle the case where jana.dot should become rec_..._dot without extra period + if [ "$dotfile" = "jana.dot" ]; then + newname="rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.dot" + fi + mv "$dotfile" "$newname" + fi + done # Convert all dot files to SVG (both main and plugin-specific files) for dotfile in *.dot; do diff --git a/docs/howto/visualize_callgraph.md b/docs/howto/visualize_callgraph.md index b76e8e5f45..867fa3e4e3 100644 --- a/docs/howto/visualize_callgraph.md +++ b/docs/howto/visualize_callgraph.md @@ -62,11 +62,26 @@ eicrecon -Pplugins=janadot \ -Pjanadot:max_edges_per_graph=60 \ sim_file.edm4hep.root -# Change splitting method (size, components, or type) +# Change splitting method (plugin, size, components, or type) eicrecon -Pplugins=janadot -Pjanadot:split_criteria=components sim_file.edm4hep.root ~~~ -When graphs are split, multiple files are created (`jana_part001.dot`, `jana_part002.dot`, etc.) along with an index file (`jana_index.txt`) that explains how to process them. +#### Plugin-based Splitting (Default) +The default splitting method groups components by detector subsystem plugins, providing both detailed subsystem views and high-level architectural overview: + +~~~bash +# Plugin-based splitting for system architecture analysis +eicrecon -Pplugins=janadot -Pjanadot:split_criteria=plugin sim_file.edm4hep.root +~~~ + +This generates: +- `jana.tracking.dot` - All tracking-related components +- `jana.ecal_barrel.dot` - ECAL barrel subsystem components +- `jana.hcal_endcap.dot` - HCAL endcap subsystem components +- `jana.dot` - Overall inter-plugin connection summary + +#### Other Splitting Methods +When graphs are split using size, components, or type methods, multiple files are created (`jana_part001.dot`, `jana_part002.dot`, etc.) along with an index file (`jana_index.txt`) that explains how to process them. ### Running for a single detector By default `eicrecon` activates the full reconstruction. This will diff --git a/src/utilities/CMakeLists.txt b/src/utilities/CMakeLists.txt index 193be84963..7af312d2aa 100644 --- a/src/utilities/CMakeLists.txt +++ b/src/utilities/CMakeLists.txt @@ -1,4 +1,4 @@ add_subdirectory(dump_flags) add_subdirectory(eicrecon) -add_subdirectory(eicjanadot) +add_subdirectory(janadot) add_subdirectory(janatop) diff --git a/src/utilities/eicjanadot/CMakeLists.txt b/src/utilities/janadot/CMakeLists.txt similarity index 100% rename from src/utilities/eicjanadot/CMakeLists.txt rename to src/utilities/janadot/CMakeLists.txt diff --git a/src/utilities/eicjanadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc similarity index 88% rename from src/utilities/eicjanadot/JEventProcessorJANADOT.cc rename to src/utilities/janadot/JEventProcessorJANADOT.cc index 2974ebff03..ebf7c72a50 100644 --- a/src/utilities/eicjanadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -26,22 +26,22 @@ void JEventProcessorJANADOT::Init() { // Set default parameter values and register them output_filename = "jana.dot"; - params->SetDefaultParameter("eicjanadot:output_file", output_filename, "Output DOT filename"); + params->SetDefaultParameter("janadot:output_file", output_filename, "Output DOT filename"); enable_splitting = true; - params->SetDefaultParameter("eicjanadot:enable_splitting", enable_splitting, + params->SetDefaultParameter("janadot:enable_splitting", enable_splitting, "Enable splitting large graphs into multiple files"); max_nodes_per_graph = 50; - params->SetDefaultParameter("eicjanadot:max_nodes_per_graph", max_nodes_per_graph, + params->SetDefaultParameter("janadot:max_nodes_per_graph", max_nodes_per_graph, "Maximum number of nodes per graph when splitting"); max_edges_per_graph = 100; - params->SetDefaultParameter("eicjanadot:max_edges_per_graph", max_edges_per_graph, + params->SetDefaultParameter("janadot:max_edges_per_graph", max_edges_per_graph, "Maximum number of edges per graph when splitting"); split_criteria = "plugin"; - params->SetDefaultParameter("eicjanadot:split_criteria", split_criteria, + params->SetDefaultParameter("janadot:split_criteria", split_criteria, "Criteria for splitting graphs: size, components, type, plugin"); } @@ -212,21 +212,47 @@ void JEventProcessorJANADOT::WriteSingleDotFile(const std::string& filename) { } void JEventProcessorJANADOT::WriteSplitDotFiles() { - if (split_criteria == "plugin") { - WritePluginDotFiles(); - return; - } - std::vector> node_groups; - - if (split_criteria == "components") { - node_groups = SplitGraphByConnectedComponents(); + std::map> plugin_groups; + + // Use switch/case for better structure + enum SplitMethod { PLUGIN, SIZE, COMPONENTS, TYPE } method = PLUGIN; + + if (split_criteria == "plugin") { + method = PLUGIN; + } else if (split_criteria == "size") { + method = SIZE; + } else if (split_criteria == "components") { + method = COMPONENTS; } else if (split_criteria == "type") { - node_groups = SplitGraphByType(); - } else { // default to "size" - node_groups = SplitGraphBySize(); + method = TYPE; + } else { + // Default to plugin if unknown + method = PLUGIN; + std::cout << "Unknown split criteria '" << split_criteria << "', defaulting to plugin" << std::endl; + } + + switch (method) { + case PLUGIN: + plugin_groups = SplitGraphByPlugin(); + WritePluginGraphs(plugin_groups); + WriteOverallDotFile(plugin_groups); + return; + + case SIZE: + node_groups = SplitGraphBySize(); + break; + + case COMPONENTS: + node_groups = SplitGraphByConnectedComponents(); + break; + + case TYPE: + node_groups = SplitGraphByType(); + break; } + // For size, components, and type splitting std::cout << "Splitting graph into " << node_groups.size() << " subgraphs" << std::endl; for (size_t i = 0; i < node_groups.size(); i++) { @@ -246,6 +272,9 @@ void JEventProcessorJANADOT::WriteSplitDotFiles() { // Write an index file explaining the split WriteIndexFile(node_groups.size()); + + // Write overall summary for all split types + WriteOverallDotFile(node_groups); } void JEventProcessorJANADOT::WriteSplitDotFile(const std::string& filename, @@ -578,9 +607,7 @@ std::vector> JEventProcessorJANADOT::SplitGraphByType() { return groups; } -void JEventProcessorJANADOT::WritePluginDotFiles() { - auto plugin_groups = SplitGraphByPlugin(); - +void JEventProcessorJANADOT::WritePluginGraphs(const std::map>& plugin_groups) { std::cout << "Splitting graph into " << plugin_groups.size() << " plugin-based subgraphs" << std::endl; @@ -589,9 +616,6 @@ void JEventProcessorJANADOT::WritePluginDotFiles() { WritePluginDotFile(plugin_name, nodes); } - // Write overall summary graph showing inter-plugin connections - WriteOverallDotFile(plugin_groups); - std::cout << std::endl; std::cout << "Factory calling information written to " << plugin_groups.size() << " plugin files plus overall summary." << std::endl; @@ -798,7 +822,9 @@ void JEventProcessorJANADOT::WriteOverallDotFile( ofs << "label=\"" << total_calls << " calls\\n"; ofs << MakeTimeString(total_time) << " (" << std::fixed << std::setprecision(1) << percent << "%)\", "; - ofs << "penwidth=2"; + // Scale penwidth linearly from 1 (0%) to 8 (100%) + double penwidth = 1.0 + (percent / 100.0) * 7.0; + ofs << "penwidth=" << std::fixed << std::setprecision(1) << penwidth; ofs << "];" << std::endl; } @@ -806,6 +832,61 @@ void JEventProcessorJANADOT::WriteOverallDotFile( ofs.close(); } +// Overloaded WriteOverallDotFile for non-plugin splitting methods +void JEventProcessorJANADOT::WriteOverallDotFile(const std::vector>& node_groups) { + std::ofstream ofs(output_filename); + if (!ofs.is_open()) { + std::cerr << "Error: Unable to open file " << output_filename << " for writing!" << std::endl; + return; + } + + // Calculate total time for percentages + double total_ms = 0.0; + for (auto& [link, stats] : call_links) { + std::string caller = MakeNametag(link.caller_name, link.caller_tag); + // Only count top-level callers (those not called by others) + bool is_top_level = true; + for (auto& [other_link, other_stats] : call_links) { + std::string other_callee = MakeNametag(other_link.callee_name, other_link.callee_tag); + if (other_callee == caller) { + is_top_level = false; + break; + } + } + if (is_top_level) { + total_ms += stats.from_factory_ms + stats.from_source_ms + stats.from_cache_ms + + stats.data_not_available_ms; + } + } + if (total_ms == 0.0) + total_ms = 1.0; + + // Write DOT file header + ofs << "digraph G {" << std::endl; + ofs << " rankdir=TB;" << std::endl; + ofs << " node [fontname=\"Arial\", fontsize=12];" << std::endl; + ofs << " edge [fontname=\"Arial\", fontsize=10];" << std::endl; + ofs << " label=\"EICrecon Overall Call Graph - " << split_criteria << " splitting summary\";" << std::endl; + ofs << " labelloc=\"t\";" << std::endl; + ofs << std::endl; + + // Create summary nodes for each group + for (size_t i = 0; i < node_groups.size(); i++) { + std::string group_name = "Group_" + std::to_string(i + 1); + ofs << " \"" << group_name << "\" ["; + ofs << "shape=box, "; + ofs << "style=filled, "; + ofs << "fillcolor=lightblue, "; + ofs << "label=\"" << group_name << "\\n(" << node_groups[i].size() << " nodes)\""; + ofs << "];" << std::endl; + } + + ofs << std::endl; + ofs << " // Note: Individual group details are in separate _partXXX.dot files" << std::endl; + ofs << "}" << std::endl; + ofs.close(); +} + std::map> JEventProcessorJANADOT::SplitGraphByPlugin() { std::map> plugin_groups; diff --git a/src/utilities/eicjanadot/JEventProcessorJANADOT.h b/src/utilities/janadot/JEventProcessorJANADOT.h similarity index 95% rename from src/utilities/eicjanadot/JEventProcessorJANADOT.h rename to src/utilities/janadot/JEventProcessorJANADOT.h index fba2b4ff55..62f2e75fc1 100644 --- a/src/utilities/eicjanadot/JEventProcessorJANADOT.h +++ b/src/utilities/janadot/JEventProcessorJANADOT.h @@ -107,9 +107,10 @@ class JEventProcessorJANADOT : public JEventProcessor { void WriteSingleDotFile(const std::string& filename); void WriteSplitDotFiles(); void WriteSplitDotFile(const std::string& filename, const std::set& nodes); - void WritePluginDotFiles(); + void WritePluginGraphs(const std::map>& plugin_groups); void WritePluginDotFile(const std::string& plugin_name, const std::set& nodes); void WriteOverallDotFile(const std::map>& plugin_groups); + void WriteOverallDotFile(const std::vector>& node_groups); void WriteIndexFile(int num_parts); std::vector> SplitGraphByConnectedComponents(); std::vector> SplitGraphBySize(); diff --git a/src/utilities/eicjanadot/README.md b/src/utilities/janadot/README.md similarity index 69% rename from src/utilities/eicjanadot/README.md rename to src/utilities/janadot/README.md index b90571079e..cbc201b0b5 100644 --- a/src/utilities/eicjanadot/README.md +++ b/src/utilities/janadot/README.md @@ -1,35 +1,35 @@ -# EIC JANADOT Plugin +# JANADOT Plugin This plugin creates DOT (graphviz) files from JANA2 call graphs for visualization purposes. ## Overview -The eicjanadot plugin records call stack information during event processing and generates DOT format files that can be processed by graphviz to create visual call graphs. It includes functionality to split large graphs into multiple smaller graphs for better processing and readability. +The janadot plugin records call stack information during event processing and generates DOT format files that can be processed by graphviz to create visual call graphs. It includes functionality to split large graphs into multiple smaller graphs for better processing and readability. ## Usage To use the plugin, add it to your eicrecon command: ```bash -eicrecon -Pplugins=eicjanadot sim_file.edm4hep.root +eicrecon -Pplugins=janadot sim_file.edm4hep.root ``` ## Configuration Parameters The plugin supports several configuration parameters: -- `eicjanadot:output_file` (default: "jana.dot") - Output DOT filename -- `eicjanadot:enable_splitting` (default: true) - Enable splitting large graphs into multiple files -- `eicjanadot:max_nodes_per_graph` (default: 50) - Maximum number of nodes per graph when splitting -- `eicjanadot:max_edges_per_graph` (default: 100) - Maximum number of edges per graph when splitting -- `eicjanadot:split_criteria` (default: "plugin") - Criteria for splitting graphs: size, components, type, plugin +- `janadot:output_file` (default: "jana.dot") - Output DOT filename +- `janadot:enable_splitting` (default: true) - Enable splitting large graphs into multiple files +- `janadot:max_nodes_per_graph` (default: 50) - Maximum number of nodes per graph when splitting +- `janadot:max_edges_per_graph` (default: 100) - Maximum number of edges per graph when splitting +- `janadot:split_criteria` (default: "plugin") - Criteria for splitting graphs: size, components, type, plugin ## Plugin-based Splitting The default splitting method groups components by detector subsystem plugins: ```bash -eicrecon -Pplugins=eicjanadot -Peicjanadot:split_criteria=plugin sim_file.root +eicrecon -Pplugins=janadot -Pjanadot:split_criteria=plugin sim_file.root ``` This generates: diff --git a/src/utilities/eicjanadot/eicjanadot.cc b/src/utilities/janadot/janadot.cc similarity index 100% rename from src/utilities/eicjanadot/eicjanadot.cc rename to src/utilities/janadot/janadot.cc From 8aa02d34e22dc8742f705d64dbd99b5fd8238190 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:31:59 +0000 Subject: [PATCH 11/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/howto/visualize_callgraph.md | 2 +- .../janadot/JEventProcessorJANADOT.cc | 52 ++++++++++--------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/docs/howto/visualize_callgraph.md b/docs/howto/visualize_callgraph.md index 867fa3e4e3..502389f89b 100644 --- a/docs/howto/visualize_callgraph.md +++ b/docs/howto/visualize_callgraph.md @@ -76,7 +76,7 @@ eicrecon -Pplugins=janadot -Pjanadot:split_criteria=plugin sim_file.edm4hep.root This generates: - `jana.tracking.dot` - All tracking-related components -- `jana.ecal_barrel.dot` - ECAL barrel subsystem components +- `jana.ecal_barrel.dot` - ECAL barrel subsystem components - `jana.hcal_endcap.dot` - HCAL endcap subsystem components - `jana.dot` - Overall inter-plugin connection summary diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index ebf7c72a50..91a36a8f36 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -214,10 +214,10 @@ void JEventProcessorJANADOT::WriteSingleDotFile(const std::string& filename) { void JEventProcessorJANADOT::WriteSplitDotFiles() { std::vector> node_groups; std::map> plugin_groups; - + // Use switch/case for better structure enum SplitMethod { PLUGIN, SIZE, COMPONENTS, TYPE } method = PLUGIN; - + if (split_criteria == "plugin") { method = PLUGIN; } else if (split_criteria == "size") { @@ -229,27 +229,28 @@ void JEventProcessorJANADOT::WriteSplitDotFiles() { } else { // Default to plugin if unknown method = PLUGIN; - std::cout << "Unknown split criteria '" << split_criteria << "', defaulting to plugin" << std::endl; + std::cout << "Unknown split criteria '" << split_criteria << "', defaulting to plugin" + << std::endl; } switch (method) { - case PLUGIN: - plugin_groups = SplitGraphByPlugin(); - WritePluginGraphs(plugin_groups); - WriteOverallDotFile(plugin_groups); - return; - - case SIZE: - node_groups = SplitGraphBySize(); - break; - - case COMPONENTS: - node_groups = SplitGraphByConnectedComponents(); - break; - - case TYPE: - node_groups = SplitGraphByType(); - break; + case PLUGIN: + plugin_groups = SplitGraphByPlugin(); + WritePluginGraphs(plugin_groups); + WriteOverallDotFile(plugin_groups); + return; + + case SIZE: + node_groups = SplitGraphBySize(); + break; + + case COMPONENTS: + node_groups = SplitGraphByConnectedComponents(); + break; + + case TYPE: + node_groups = SplitGraphByType(); + break; } // For size, components, and type splitting @@ -272,7 +273,7 @@ void JEventProcessorJANADOT::WriteSplitDotFiles() { // Write an index file explaining the split WriteIndexFile(node_groups.size()); - + // Write overall summary for all split types WriteOverallDotFile(node_groups); } @@ -607,7 +608,8 @@ std::vector> JEventProcessorJANADOT::SplitGraphByType() { return groups; } -void JEventProcessorJANADOT::WritePluginGraphs(const std::map>& plugin_groups) { +void JEventProcessorJANADOT::WritePluginGraphs( + const std::map>& plugin_groups) { std::cout << "Splitting graph into " << plugin_groups.size() << " plugin-based subgraphs" << std::endl; @@ -833,7 +835,8 @@ void JEventProcessorJANADOT::WriteOverallDotFile( } // Overloaded WriteOverallDotFile for non-plugin splitting methods -void JEventProcessorJANADOT::WriteOverallDotFile(const std::vector>& node_groups) { +void JEventProcessorJANADOT::WriteOverallDotFile( + const std::vector>& node_groups) { std::ofstream ofs(output_filename); if (!ofs.is_open()) { std::cerr << "Error: Unable to open file " << output_filename << " for writing!" << std::endl; @@ -866,7 +869,8 @@ void JEventProcessorJANADOT::WriteOverallDotFile(const std::vector Date: Sat, 23 Aug 2025 17:10:08 +0000 Subject: [PATCH 12/29] Fix workflow to use janadot plugin instead of eicjanadot Co-authored-by: wdconinc <4656391+wdconinc@users.noreply.github.com> --- .github/workflows/linux-eic-shell.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux-eic-shell.yml b/.github/workflows/linux-eic-shell.yml index 202115a94c..7ffb6f1bd4 100644 --- a/.github/workflows/linux-eic-shell.yml +++ b/.github/workflows/linux-eic-shell.yml @@ -549,7 +549,7 @@ jobs: export DETECTOR_CONFIG=${DETECTOR}_${{ matrix.detector_config }} export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins${JANA_PLUGIN_PATH:+:${JANA_PLUGIN_PATH}} - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_collections=EventHeader,MCParticles,EcalBarrelScFiRawHits,EcalBarrelImagingRawHits -Ppodio:output_file=two_stage_raw_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,eicjanadot -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_collections=EventHeader,MCParticles,EcalBarrelScFiRawHits,EcalBarrelImagingRawHits -Ppodio:output_file=two_stage_raw_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,janadot -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json - name: Upload digitization output uses: actions/upload-artifact@v4 with: @@ -565,7 +565,7 @@ jobs: export DETECTOR_CONFIG=${DETECTOR}_${{ matrix.detector_config }} export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins${JANA_PLUGIN_PATH:+:${JANA_PLUGIN_PATH}} - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_collections=EcalBarrelClusters,EcalBarrelClusterAssociations -Ppodio:output_file=two_stage_rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root two_stage_raw_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root -Pplugins=dump_flags,eicjanadot -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_collections=EcalBarrelClusters,EcalBarrelClusterAssociations -Ppodio:output_file=two_stage_rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root two_stage_raw_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root -Pplugins=dump_flags,janadot -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json - name: Upload reconstruction output uses: actions/upload-artifact@v4 with: @@ -824,7 +824,7 @@ jobs: export DETECTOR_CONFIG=${DETECTOR}_${{ matrix.detector_config }} export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4hep.root -Ppodio:output_collections=EcalLumiSpecRawHits,EcalLumiSpecRecHits,EcalLumiSpecClusters,EcalLumiSpecClusterAssociations -PLUMISPECCAL:EcalLumiSpecIslandProtoClusters:splitCluster=1 -Pplugins=dump_flags,eicjanadot,janatop -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Peicjanadot:split_criteria=plugin + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4hep.root -Ppodio:output_collections=EcalLumiSpecRawHits,EcalLumiSpecRecHits,EcalLumiSpecClusters,EcalLumiSpecClusterAssociations -PLUMISPECCAL:EcalLumiSpecIslandProtoClusters:splitCluster=1 -Pplugins=dump_flags,janadot,janatop -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Pjanadot:split_criteria=plugin - uses: actions/upload-artifact@v4 with: name: rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root @@ -936,7 +936,7 @@ jobs: export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins prmon --json-summary rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.prmon.json -- \ - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{matrix.nthreads > 1 && format('-Pnthreads={0}', matrix.nthreads) || ''}} -Ppodio:output_file=rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root sim_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}.edm4hep.root -Pacts:WriteObj=true -Pacts:WritePly=true -Pplugins=eicjanadot,janatop -Peicjanadot:split_criteria=plugin + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{matrix.nthreads > 1 && format('-Pnthreads={0}', matrix.nthreads) || ''}} -Ppodio:output_file=rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root sim_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}.edm4hep.root -Pacts:WriteObj=true -Pacts:WritePly=true -Pplugins=janadot,janatop -Pjanadot:split_criteria=plugin - uses: actions/upload-artifact@v4 with: name: rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root From 700d41af1a70add0e7c9dbd444498a66f0719651 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 23 Aug 2025 12:13:11 -0500 Subject: [PATCH 13/29] Remove default plugin split_criteria option to minimize workflow definition changeset --- .github/workflows/linux-eic-shell.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux-eic-shell.yml b/.github/workflows/linux-eic-shell.yml index 7ffb6f1bd4..fc8ab03ab8 100644 --- a/.github/workflows/linux-eic-shell.yml +++ b/.github/workflows/linux-eic-shell.yml @@ -709,7 +709,7 @@ jobs: export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins prmon --json-summary rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.prmon.json -- \ - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,janadot,janatop -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Pjanadot:split_criteria=plugin + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,janadot,janatop -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json - uses: actions/upload-artifact@v4 with: name: rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root @@ -824,7 +824,7 @@ jobs: export DETECTOR_CONFIG=${DETECTOR}_${{ matrix.detector_config }} export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4hep.root -Ppodio:output_collections=EcalLumiSpecRawHits,EcalLumiSpecRecHits,EcalLumiSpecClusters,EcalLumiSpecClusterAssociations -PLUMISPECCAL:EcalLumiSpecIslandProtoClusters:splitCluster=1 -Pplugins=dump_flags,janadot,janatop -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json -Pjanadot:split_criteria=plugin + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4hep.root -Ppodio:output_collections=EcalLumiSpecRawHits,EcalLumiSpecRecHits,EcalLumiSpecClusters,EcalLumiSpecClusterAssociations -PLUMISPECCAL:EcalLumiSpecIslandProtoClusters:splitCluster=1 -Pplugins=dump_flags,janadot,janatop -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json - uses: actions/upload-artifact@v4 with: name: rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root @@ -936,7 +936,7 @@ jobs: export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins prmon --json-summary rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.prmon.json -- \ - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{matrix.nthreads > 1 && format('-Pnthreads={0}', matrix.nthreads) || ''}} -Ppodio:output_file=rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root sim_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}.edm4hep.root -Pacts:WriteObj=true -Pacts:WritePly=true -Pplugins=janadot,janatop -Pjanadot:split_criteria=plugin + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{matrix.nthreads > 1 && format('-Pnthreads={0}', matrix.nthreads) || ''}} -Ppodio:output_file=rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root sim_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}.edm4hep.root -Pacts:WriteObj=true -Pacts:WritePly=true -Pplugins=janadot,janatop - uses: actions/upload-artifact@v4 with: name: rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root From 8be784d8f87385edeceb92939c44a7bc8f41ce04 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 18:18:27 +0000 Subject: [PATCH 14/29] Address review comments: update default split criteria, MakeNametag format, and plugin detection Co-authored-by: wdconinc <4656391+wdconinc@users.noreply.github.com> --- .../janadot/JEventProcessorJANADOT.cc | 44 ++++++++++++++++--- .../janadot/JEventProcessorJANADOT.h | 1 + src/utilities/janadot/README.md | 12 ++--- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index 91a36a8f36..f325781949 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -40,7 +41,7 @@ void JEventProcessorJANADOT::Init() { params->SetDefaultParameter("janadot:max_edges_per_graph", max_edges_per_graph, "Maximum number of edges per graph when splitting"); - split_criteria = "plugin"; + split_criteria = "size"; params->SetDefaultParameter("janadot:split_criteria", split_criteria, "Criteria for splitting graphs: size, components, type, plugin"); } @@ -52,6 +53,24 @@ void JEventProcessorJANADOT::Process(const std::shared_ptr& event) // Lock mutex in case we are running with multiple threads std::lock_guard lck(mutex); + // Build mapping of nametags to plugin names (only do this once per execution) + static bool factory_mapping_built = false; + if (!factory_mapping_built) { + auto factories = event->GetFactorySet()->GetAllFactories(); + for (auto* factory : factories) { + std::string nametag = MakeNametag(factory->GetObjectName(), factory->GetTag()); + std::string plugin_name = factory->GetPluginName(); + + // If plugin name is empty, try to use a reasonable default + if (plugin_name.empty()) { + plugin_name = "core"; + } + + nametag_to_plugin[nametag] = plugin_name; + } + factory_mapping_built = true; + } + // Loop over the call stack elements and add in the values for (unsigned int i = 0; i < stack.size(); i++) { @@ -428,9 +447,12 @@ std::string JEventProcessorJANADOT::MakeTimeString(double time_in_ms) { } std::string JEventProcessorJANADOT::MakeNametag(const std::string& name, const std::string& tag) { - std::string nametag = name; - if (tag.size() > 0) - nametag += ":" + tag; + std::string nametag = tag; + if (tag.size() > 0 && name.size() > 0) { + nametag += " (" + name + ")"; + } else if (name.size() > 0) { + nametag = name; + } return nametag; } @@ -894,9 +916,19 @@ void JEventProcessorJANADOT::WriteOverallDotFile( std::map> JEventProcessorJANADOT::SplitGraphByPlugin() { std::map> plugin_groups; - // Group nodes by their detected plugin + // Group nodes by their actual plugin (from factory information) for (auto& [nametag, fstats] : factory_stats) { - std::string plugin = ExtractPluginName(nametag); + std::string plugin; + + // Try to get plugin from our mapping first + auto it = nametag_to_plugin.find(nametag); + if (it != nametag_to_plugin.end()) { + plugin = it->second; + } else { + // Fall back to heuristic extraction for items not in factory set + plugin = ExtractPluginName(nametag); + } + plugin_groups[plugin].insert(nametag); } diff --git a/src/utilities/janadot/JEventProcessorJANADOT.h b/src/utilities/janadot/JEventProcessorJANADOT.h index 62f2e75fc1..2a917a534e 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.h +++ b/src/utilities/janadot/JEventProcessorJANADOT.h @@ -87,6 +87,7 @@ class JEventProcessorJANADOT : public JEventProcessor { std::map call_links; std::map factory_stats; + std::map nametag_to_plugin; // Maps nametag to plugin name // Configuration parameters std::string output_filename; diff --git a/src/utilities/janadot/README.md b/src/utilities/janadot/README.md index cbc201b0b5..c188b98b77 100644 --- a/src/utilities/janadot/README.md +++ b/src/utilities/janadot/README.md @@ -22,11 +22,11 @@ The plugin supports several configuration parameters: - `janadot:enable_splitting` (default: true) - Enable splitting large graphs into multiple files - `janadot:max_nodes_per_graph` (default: 50) - Maximum number of nodes per graph when splitting - `janadot:max_edges_per_graph` (default: 100) - Maximum number of edges per graph when splitting -- `janadot:split_criteria` (default: "plugin") - Criteria for splitting graphs: size, components, type, plugin +- `janadot:split_criteria` (default: "size") - Criteria for splitting graphs: size, components, type, plugin ## Plugin-based Splitting -The default splitting method groups components by detector subsystem plugins: +To use plugin-based splitting to group components by detector subsystem plugins: ```bash eicrecon -Pplugins=janadot -Pjanadot:split_criteria=plugin sim_file.root @@ -67,12 +67,12 @@ dot -Tpdf jana.ecal_barrel.dot -o jana.ecal_barrel.pdf ## Graph Splitting Methods -### Plugin-based Splitting (default) -Groups components by detector subsystem, providing both detailed subsystem views and high-level architectural overview. - -### Size-based Splitting +### Size-based Splitting (default) Nodes are grouped to keep within the specified limits of nodes and edges per graph. +### Plugin-based Splitting +Groups components by detector subsystem, providing both detailed subsystem views and high-level architectural overview. + ### Component-based Splitting Uses connected components analysis to group nodes that are connected by call relationships. From 3c513cfd8203299d9a01044f630647eebf02865e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 18:28:17 +0000 Subject: [PATCH 15/29] Change default split_criteria back to plugin Co-authored-by: wdconinc <4656391+wdconinc@users.noreply.github.com> --- src/utilities/janadot/JEventProcessorJANADOT.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index f325781949..cde130c6da 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -41,7 +41,7 @@ void JEventProcessorJANADOT::Init() { params->SetDefaultParameter("janadot:max_edges_per_graph", max_edges_per_graph, "Maximum number of edges per graph when splitting"); - split_criteria = "size"; + split_criteria = "plugin"; params->SetDefaultParameter("janadot:split_criteria", split_criteria, "Criteria for splitting graphs: size, components, type, plugin"); } From 5d40aedcc2518128fda0e2b38223846dd7306f1f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Aug 2025 18:29:41 +0000 Subject: [PATCH 16/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/utilities/janadot/JEventProcessorJANADOT.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index cde130c6da..d2daf3070f 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -58,14 +58,14 @@ void JEventProcessorJANADOT::Process(const std::shared_ptr& event) if (!factory_mapping_built) { auto factories = event->GetFactorySet()->GetAllFactories(); for (auto* factory : factories) { - std::string nametag = MakeNametag(factory->GetObjectName(), factory->GetTag()); + std::string nametag = MakeNametag(factory->GetObjectName(), factory->GetTag()); std::string plugin_name = factory->GetPluginName(); - + // If plugin name is empty, try to use a reasonable default if (plugin_name.empty()) { plugin_name = "core"; } - + nametag_to_plugin[nametag] = plugin_name; } factory_mapping_built = true; @@ -919,7 +919,7 @@ std::map> JEventProcessorJANADOT::SplitGraphB // Group nodes by their actual plugin (from factory information) for (auto& [nametag, fstats] : factory_stats) { std::string plugin; - + // Try to get plugin from our mapping first auto it = nametag_to_plugin.find(nametag); if (it != nametag_to_plugin.end()) { @@ -928,7 +928,7 @@ std::map> JEventProcessorJANADOT::SplitGraphB // Fall back to heuristic extraction for items not in factory set plugin = ExtractPluginName(nametag); } - + plugin_groups[plugin].insert(nametag); } From f886b6df9d03e8b4b44e98e0266cda8521f09bba Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 23 Aug 2025 14:27:45 -0500 Subject: [PATCH 17/29] Test factory nametags and plugin_names --- src/utilities/janadot/JEventProcessorJANADOT.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index d2daf3070f..9c65404d36 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -60,6 +60,7 @@ void JEventProcessorJANADOT::Process(const std::shared_ptr& event) for (auto* factory : factories) { std::string nametag = MakeNametag(factory->GetObjectName(), factory->GetTag()); std::string plugin_name = factory->GetPluginName(); + std::cout << nametag << " " << plugin_name << std::endl; // If plugin name is empty, try to use a reasonable default if (plugin_name.empty()) { From 4b6cddf939f1f22c2a48c09c8490ad7458a28661 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 23 Aug 2025 14:28:08 -0500 Subject: [PATCH 18/29] Add janadot plugin with enhanced plugin-based graph splitting and inter-plugin visualization (fix: iwyu) (#2042) This PR applies the include-what-you-use fixes as suggested by https://github.com/eic/EICrecon/actions/runs/17178775314. Please merge this PR into the branch `copilot/fix-fa2a3ce4-8a21-441e-9641-77391e5b455f` to resolve failures in PR #2040. Auto-generated by [create-pull-request][1] [1]: https://github.com/peter-evans/create-pull-request Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/utilities/janadot/JEventProcessorJANADOT.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index 9c65404d36..814f6efa05 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -5,6 +5,7 @@ #include #include +#include #include #include #include From 3a793d5568c52102dbc2d15e65e88f5b20fbbeec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 21:56:59 +0000 Subject: [PATCH 19/29] Re-add janadot groups functionality and .github/janadot.groups file - Restore .github/janadot.groups with comprehensive group definitions - Add group-based splitting with split_criteria=groups option - Implement LoadGroupDefinitions() to parse group file and command line parameters - Add SplitGraphByGroups() and WriteGroupGraphs() functions - Support both file-based and command line group definitions - Generate separate jana.GroupName.dot files per group - Update documentation with groups usage examples - Add necessary includes for string stream processing Co-authored-by: wdconinc <4656391+wdconinc@users.noreply.github.com> --- .github/janadot.groups | 46 +++++ .../janadot/JEventProcessorJANADOT.cc | 193 +++++++++++++++++- .../janadot/JEventProcessorJANADOT.h | 9 + src/utilities/janadot/README.md | 28 ++- 4 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 .github/janadot.groups diff --git a/.github/janadot.groups b/.github/janadot.groups new file mode 100644 index 0000000000..c905949931 --- /dev/null +++ b/.github/janadot.groups @@ -0,0 +1,46 @@ +-PJANADOT:GROUP:EcalEndcapN=edm4hep::SimCalorimeterHit:EcalEndcapNHits,edm4hep::RawCalorimeterHit:EcalEndcapNRawHits,edm4eic::CalorimeterHit:EcalEndcapNRecHits,edm4eic::ProtoCluster:EcalEndcapNIslandProtoClusters,edm4eic::Cluster:EcalEndcapNClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapNClusterAssociations,edm4eic::ProtoCluster:EcalEndcapNTruthProtoClusters,edm4eic::Cluster:EcalEndcapNTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapNTruthClusterAssociations,color_blue +-PJANADOT:GROUP:EcalBarrelScFi=edm4hep::SimCalorimeterHit:EcalBarrelScFiHits,edm4hep::RawCalorimeterHit:EcalBarrelScFiRawHits,edm4eic::CalorimeterHit:EcalBarrelScFiRecHits,edm4eic::ProtoCluster:EcalBarrelScFiProtoClusters,edm4eic::Cluster:EcalBarrelScFiClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelScFiClusterAssociations,color_red +-PJANADOT:GROUP:EcalBarrelImaging=edm4hep::SimCalorimeterHit:EcalBarrelImagingHits,edm4hep::RawCalorimeterHit:EcalBarrelImagingRawHits,edm4eic::CalorimeterHit:EcalBarrelImagingRecHits,edm4eic::ProtoCluster:EcalBarrelImagingProtoClusters,edm4eic::Cluster:EcalBarrelImagingClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelImagingClusterAssociations,color_blue +-PJANADOT:GROUP:EcalBarrel=edm4eic::Cluster:EcalBarrelClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelClusterAssociations,edm4eic::Cluster:EcalBarrelTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelTruthClusterAssociations,color_blue +-PJANADOT:GROUP:Ecal=edm4eic::Cluster:EcalClusters,edm4eic::MCRecoClusterParticleAssociation:EcalClusterAssociations,color_blue +-PJANADOT:GROUP:EcalEndcapP=edm4hep::SimCalorimeterHit:EcalEndcapPHits,edm4hep::RawCalorimeterHit:EcalEndcapPRawHits,edm4eic::CalorimeterHit:EcalEndcapPRecHits,edm4eic::ProtoCluster:EcalEndcapPIslandProtoClusters,edm4eic::Cluster:EcalEndcapPClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapPClusterAssociations,edm4eic::ProtoCluster:EcalEndcapPTruthProtoClusters,edm4eic::Cluster:EcalEndcapPTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapPTruthClusterAssociations,color_blue +-PJANADOT:GROUP:B0Ecal=edm4hep::SimCalorimeterHit:B0ECalHits,edm4hep::RawCalorimeterHit:B0ECalRawHits,edm4eic::CalorimeterHit:B0ECalRecHits,edm4eic::ProtoCluster:B0ECalIslandProtoClusters,edm4eic::Cluster:B0ECalClusters,edm4eic::MCRecoClusterParticleAssociation:B0ECalClusterAssociations,color_blue +-PJANADOT:GROUP:EcalFarForwardZDC=edm4hep::SimCalorimeterHit:EcalFarForwardZDCHits,edm4hep::RawCalorimeterHit:EcalFarForwardZDCRawHits,edm4eic::CalorimeterHit:EcalFarForwardZDCRecHits,edm4eic::CalorimeterHit:EcalFarForwardZDCSubcellHits,edm4eic::ProtoCluster:EcalFarForwardZDCImagingProtoClusters,edm4eic::ProtoCluster:EcalFarForwardZDCIslandProtoClusters,edm4eic::Cluster:EcalFarForwardZDCClusters,edm4eic::MCRecoClusterParticleAssociation:EcalFarForwardZDCClusterAssociations,edm4eic::ProtoCluster:EcalFarForwardZDCTruthProtoClusters,edm4eic::Cluster:EcalFarForwardZDCTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalFarForwardZDCTruthClusterAssociations,color_red +-PJANADOT:GROUP:EcalLumiSpec=edm4hep::SimCalorimeterHit:EcalLumiSpecHits,edm4hep::RawCalorimeterHit:EcalLumiSpecRawHits,edm4eic::CalorimeterHit:EcalLumiSpecRecHits,edm4eic::CalorimeterHit:EcalLumiSpecSubcellHits,edm4eic::ProtoCluster:EcalLumiSpecImagingProtoClusters,edm4eic::ProtoCluster:EcalLumiSpecIslandProtoClusters,edm4eic::Cluster:EcalLumiSpecClusters,edm4eic::MCRecoClusterParticleAssociation:EcalLumiSpecClusterAssociations,edm4eic::ProtoCluster:EcalLumiSpecTruthProtoClusters,edm4eic::Cluster:EcalLumiSpecTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalLumiSpecTruthClusterAssociations,color_red + +-PJANADOT:GROUP:HcalEndcapN=edm4hep::SimCalorimeterHit:HcalEndcapNHits,edm4hep::RawCalorimeterHit:HcalEndcapNRawHits,edm4eic::CalorimeterHit:HcalEndcapNRecHits,edm4eic::CalorimeterHit:HcalEndcapNMergedHits,edm4eic::ProtoCluster:HcalEndcapNIslandProtoClusters,edm4eic::Cluster:HcalEndcapNClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapNClusterAssociations,edm4eic::ProtoCluster:HcalEndcapNTruthProtoClusters,edm4eic::Cluster:HcalEndcapNTruthClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapNTruthClusterAssociations,color_red +-PJANADOT:GROUP:HcalBarrel=edm4hep::SimCalorimeterHit:HcalBarrelHits,edm4hep::RawCalorimeterHit:HcalBarrelRawHits,edm4eic::CalorimeterHit:HcalBarrelRecHits,edm4eic::ProtoCluster:HcalBarrelIslandProtoClusters,edm4eic::Cluster:HcalBarrelClusters,edm4eic::MCRecoClusterParticleAssociation:HcalBarrelClusterAssociations,edm4eic::ProtoCluster:HcalBarrelTruthProtoClusters,edm4eic::Cluster:HcalBarrelTruthClusters,edm4eic::MCRecoClusterParticleAssociation:HcalBarrelTruthClusterAssociations,color_red +-PJANADOT:GROUP:HcalEndcapP=edm4hep::SimCalorimeterHit:HcalEndcapPHits,edm4hep::RawCalorimeterHit:HcalEndcapPRawHits,edm4eic::CalorimeterHit:HcalEndcapPRecHits,edm4eic::ProtoCluster:HcalEndcapPIslandProtoClusters,edm4eic::Cluster:HcalEndcapPClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapPClusterAssociations,color_red +-PJANADOT:GROUP:LFHCAL=edm4hep::SimCalorimeterHit:LFHCALHits,edm4hep::RawCalorimeterHit:LFHCALRawHits,edm4eic::CalorimeterHit:LFHCALRecHits,edm4eic::ProtoCluster:LFHCALIslandProtoClusters,edm4eic::Cluster:LFHCALClusters,edm4eic::MCRecoClusterParticleAssociation:LFHCALClusterAssociations,color_red +-PJANADOT:GROUP:HcalEndcapPInsert=edm4hep::SimCalorimeterHit:HcalEndcapPInsertHits,edm4hep::RawCalorimeterHit:HcalEndcapPInsertRawHits,edm4eic::CalorimeterHit:HcalEndcapPInsertRecHits,edm4eic::CalorimeterHit:HcalEndcapPInsertMergedHits,edm4eic::ProtoCluster:HcalEndcapPInsertIslandProtoClusters,edm4eic::Cluster:HcalEndcapPInsertClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapPInsertClusterAssociations,color_red +-PJANADOT:GROUP:HcalFarForwardZDC=edm4hep::SimCalorimeterHit:HcalFarForwardZDCHits,edm4hep::RawCalorimeterHit:HcalFarForwardZDCRawHits,edm4eic::CalorimeterHit:HcalFarForwardZDCRecHits,edm4eic::CalorimeterHit:HcalFarForwardZDCSubcellHits,edm4eic::ProtoCluster:HcalFarForwardZDCImagingProtoClusters,edm4eic::ProtoCluster:HcalFarForwardZDCIslandProtoClusters,edm4eic::Cluster:HcalFarForwardZDCClusters,edm4eic::MCRecoClusterParticleAssociation:HcalFarForwardZDCClusterAssociations,edm4eic::ProtoCluster:HcalFarForwardZDCTruthProtoClusters,edm4eic::Cluster:HcalFarForwardZDCTruthClusters,edm4eic::MCRecoClusterParticleAssociation:HcalFarForwardZDCTruthClusterAssociations,edm4eic::ProtoCluster:HcalFarForwardZDCIslandProtoClustersBaseline,edm4eic::Cluster:HcalFarForwardZDCClustersBaseline,edm4eic::MCRecoClusterParticleAssociation:HcalFarForwardZDCClusterAssociationsBaseline,color_red +-PJANADOT:GROUP:RICHEndcapN=edm4hep::SimTrackerHit:RICHEndcapNHits,edm4eic::RawTrackerHit:RICHEndcapNRawHits,edm4eic::MCRecoTrackerHitAssociation:RICHEndcapNRawHitsAssociations,color_green +-PJANADOT:GROUP:DIRC=edm4hep::SimTrackerHit:DIRCBarHits,edm4eic::RawTrackerHit:DIRCRawHits,color_green + +-PJANADOT:GROUP:Tracking=edm4eic::TrackerHit:CentralTrackingRecHits,edm4eic::TrackParameters:CentralTrackSeedingResults,edm4eic::Measurement2D:CentralTrackerMeasurements,edm4eic::TrackParameters:CentralCKFSeededTrackParameters,ActsExamples::Trajectories:CentralCKFActsTrajectories,Acts::TrackContainer:CentralCKFActsTracks,edm4eic::TrackParameters:CentralCKFTrackParameters,edm4eic::Track:CentralCKFTracks + +-PJANADOT:GROUP:B0Tracker=edm4hep::SimTrackerHit:B0TrackerHits,edm4eic::TrackerHit:B0TrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:B0TrackerRecHitsAssociations,color_green + +-PJANADOT:GROUP:TrackerBarrel=edm4hep::SimTrackerHit:SiBarrelHits,edm4eic::TrackerHit:SiBarrelTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:SiBarrelTrackerRecHitsAssociations,color_green +-PJANADOT:GROUP:TrackerEndcapN=edm4hep::SimTrackerHit:SiEndcapNHits,edm4eic::TrackerHit:SiEndcapNTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:SiEndcapNTrackerRecHitsAssociations,color_green +-PJANADOT:GROUP:TrackerEndcapP=edm4hep::SimTrackerHit:SiEndcapPHits,edm4eic::TrackerHit:SiEndcapPTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:SiEndcapPTrackerRecHitsAssociations,color_green + +-PJANADOT:GROUP:GEMEndcapN=edm4hep::SimTrackerHit:GEMEndcapNHits,edm4eic::TrackerHit:GEMEndcapNTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:GEMEndcapNTrackerRecHitsAssociations,color_green +-PJANADOT:GROUP:MPGDBarrel=edm4hep::SimTrackerHit:MPGDBarrelHits,edm4eic::TrackerHit:MPGDBarrelTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:MPGDBarrelTrackerRecHitsAssociations,color_green + +-PJANADOT:GROUP:TOFBarrel=edm4hep::SimTrackerHit:TOFBarrelHits,edm4eic::TrackerHit:TOFBarrelRecHits,edm4eic::MCRecoTrackerHitAssociation:TOFBarrelRecHitsAssociations,color_green +-PJANADOT:GROUP:TOFEndcapN=edm4hep::SimTrackerHit:TOFEndcapHits,edm4eic::TrackerHit:TOFEndcapRecHits,edm4eic::MCRecoTrackerHitAssociation:TOFEndcapRecHitsAssociations,color_green + +-PJANADOT:GROUP:TruthTracks=edm4eic::Track:TruthTracks,color_lightblue +-PJANADOT:GROUP:Vertices=edm4eic::Vertex:CentralCKFVertices,edm4eic::Vertex:TruthPrimaryVertices,color_purple + +-PJANADOT:GROUP:MCParticles=edm4hep::MCParticle:MCParticles,color_orange +-PJANADOT:GROUP:InputOutput=edm4hep::EventHeader:EventHeader,color_gray + +-PJANADOT:GROUP:Jets=edm4eic::InclusiveKinematicsDA:InclusiveKinematicsDA,edm4eic::InclusiveKinematicsElectron:InclusiveKinematicsElectron,edm4eic::InclusiveKinematicsJB:InclusiveKinematicsJB,edm4eic::InclusiveKinematicsSigma:InclusiveKinematicsSigma,edm4eic::InclusiveKinematicseSigma:InclusiveKinematicseSigma,edm4eic::ReconstructedParticle:ReconstructedChargedParticles,edm4eic::ReconstructedParticle:ReconstructedNeutralParticles,edm4eic::ReconstructedParticle:ReconstructedParticles,edm4eic::MCRecoParticleAssociation:ReconstructedChargedParticleAssociations,edm4eic::MCRecoParticleAssociation:ReconstructedNeutralParticleAssociations,edm4eic::MCRecoParticleAssociation:ReconstructedParticleAssociations,edm4eic::ReconstructedParticle:ScatteredElectronsTruth,edm4eic::ReconstructedParticle:GeneratedJets,edm4eic::ReconstructedParticle:ReconstructedJets,edm4eic::MCRecoParticleAssociation:GeneratedChargedJetsAssociations,edm4eic::MCRecoParticleAssociation:ReconstructedJetsAssociations,color_cyan + +-PJANADOT:GROUP:GeneratedParticles=edm4hep::GeneratedParticles,color_lightyellow + +-PJANADOT:GROUP:FarForward=edm4hep::SimTrackerHit:ForwardOffMTrackerHits,edm4eic::TrackerHit:ForwardOffMTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:ForwardOffMTrackerRecHitsAssociations,edm4hep::SimTrackerHit:ForwardRomanPotHits,edm4eic::TrackerHit:ForwardRomanPotRecHits,edm4eic::MCRecoTrackerHitAssociation:ForwardRomanPotRecHitsAssociations,color_brown + +-PJANADOT:GROUP:ParticleID=edm4eic::CherenkovParticleID:PFRICH_PID,edm4eic::CherenkovParticleIDHypothesis:PFRICH_PID_Hypothesis,edm4eic::dRICHAerogelIrtCherenkovParticleID:dRICHAerogelIrtCherenkovParticleID,edm4eic::dRICHGasIrtCherenkovParticleID:dRICHGasIrtCherenkovParticleID,color_pink \ No newline at end of file diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index 814f6efa05..92a90897e8 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -44,7 +45,42 @@ void JEventProcessorJANADOT::Init() { split_criteria = "plugin"; params->SetDefaultParameter("janadot:split_criteria", split_criteria, - "Criteria for splitting graphs: size, components, type, plugin"); + "Criteria for splitting graphs: size, components, type, plugin, groups"); + + // Load group definitions if they exist + LoadGroupDefinitions(); + + // Also check for JANADOT:GROUP parameters (command line group definitions) + auto parameter_keys = params->GetParameterNames(); + for (const auto& key : parameter_keys) { + if (key.find("janadot:group:") == 0) { + std::string group_name = key.substr(13); // Remove "janadot:group:" prefix + std::string group_definition; + params->GetParameter(key, group_definition); + + // Parse command line group definition similar to file format + std::vector factories; + std::string color = "lightblue"; // default color + + std::stringstream ss(group_definition); + std::string item; + while (std::getline(ss, item, ',')) { + if (item.find("color_") == 0) { + color = item.substr(6); // remove "color_" prefix + } else if (!item.empty()) { + factories.push_back(item); + } + } + + user_groups[group_name] = factories; + user_group_colors[group_name] = color; + + // Build nametag to group mapping + for (const auto& factory : factories) { + nametag_to_group[factory] = group_name; + } + } + } } void JEventProcessorJANADOT::Process(const std::shared_ptr& event) { @@ -237,7 +273,7 @@ void JEventProcessorJANADOT::WriteSplitDotFiles() { std::map> plugin_groups; // Use switch/case for better structure - enum SplitMethod { PLUGIN, SIZE, COMPONENTS, TYPE } method = PLUGIN; + enum SplitMethod { PLUGIN, SIZE, COMPONENTS, TYPE, GROUPS } method = PLUGIN; if (split_criteria == "plugin") { method = PLUGIN; @@ -247,6 +283,8 @@ void JEventProcessorJANADOT::WriteSplitDotFiles() { method = COMPONENTS; } else if (split_criteria == "type") { method = TYPE; + } else if (split_criteria == "groups") { + method = GROUPS; } else { // Default to plugin if unknown method = PLUGIN; @@ -261,6 +299,12 @@ void JEventProcessorJANADOT::WriteSplitDotFiles() { WriteOverallDotFile(plugin_groups); return; + case GROUPS: + plugin_groups = SplitGraphByGroups(); + WriteGroupGraphs(plugin_groups); + WriteOverallDotFile(plugin_groups); + return; + case SIZE: node_groups = SplitGraphBySize(); break; @@ -1034,3 +1078,148 @@ std::string JEventProcessorJANADOT::ExtractPluginName(const std::string& nametag // Final fallback return "misc"; } + +void JEventProcessorJANADOT::LoadGroupDefinitions() { + // Load group definitions from .github/janadot.groups file + std::ifstream file(".github/janadot.groups"); + if (!file.is_open()) { + std::cout << "Warning: Could not open .github/janadot.groups file. Group splitting may not work properly." << std::endl; + return; + } + + std::string line; + while (std::getline(file, line)) { + // Skip empty lines and comments + if (line.empty() || line[0] == '#') { + continue; + } + + // Parse lines of format: -PJANADOT:GROUP:GroupName=factory1,factory2,...,color + if (line.find("-PJANADOT:GROUP:") == 0) { + size_t group_start = line.find("GROUP:") + 6; + size_t equals_pos = line.find("=", group_start); + if (equals_pos == std::string::npos) continue; + + std::string group_name = line.substr(group_start, equals_pos - group_start); + std::string factories_str = line.substr(equals_pos + 1); + + // Parse comma-separated list of factories and color + std::vector factories; + std::string color = "lightblue"; // default color + + std::stringstream ss(factories_str); + std::string item; + while (std::getline(ss, item, ',')) { + if (item.find("color_") == 0) { + color = item.substr(6); // remove "color_" prefix + } else if (!item.empty()) { + factories.push_back(item); + } + } + + user_groups[group_name] = factories; + user_group_colors[group_name] = color; + + // Build nametag to group mapping + for (const auto& factory : factories) { + nametag_to_group[factory] = group_name; + } + } + } + file.close(); + + std::cout << "Loaded " << user_groups.size() << " group definitions from .github/janadot.groups" << std::endl; +} + +std::map> JEventProcessorJANADOT::SplitGraphByGroups() { + std::map> groups; + + // Group nodes based on user-defined groups + for (auto& [nametag, fstats] : factory_stats) { + std::string group_name = "Ungrouped"; + + // Check if this factory is in any user-defined group + if (nametag_to_group.find(nametag) != nametag_to_group.end()) { + group_name = nametag_to_group[nametag]; + } + + groups[group_name].insert(nametag); + } + + return groups; +} + +void JEventProcessorJANADOT::WriteGroupGraphs(const std::map>& groups) { + std::cout << "Splitting graph into " << groups.size() << " group-based subgraphs" << std::endl; + + for (auto& [group_name, nodes] : groups) { + WriteGroupDotFile(group_name, nodes); + } + + std::cout << "Factory calling information written to " << groups.size() + << " group-based dot files. Use graphviz to convert to images." << std::endl; +} + +void JEventProcessorJANADOT::WriteGroupDotFile(const std::string& group_name, const std::set& nodes) { + // Create filename like jana.GroupName.dot + std::string base_filename = output_filename; + size_t dot_pos = base_filename.find_last_of('.'); + if (dot_pos != std::string::npos) { + base_filename = base_filename.substr(0, dot_pos); + } + + std::string filename = base_filename + "." + group_name + ".dot"; + std::ofstream ofs(filename); + + ofs << "digraph G {" << std::endl; + ofs << " rankdir=LR;" << std::endl; + ofs << " node [fontsize=10,style=filled];" << std::endl; + ofs << " edge [fontsize=8];" << std::endl; + ofs << std::endl; + + // Get group color + std::string color = "lightblue"; + if (user_group_colors.find(group_name) != user_group_colors.end()) { + color = user_group_colors[group_name]; + } + + ofs << " label=\"EICrecon Call Graph - " << group_name << " Group\";" << std::endl; + ofs << " labeljust=c;" << std::endl; + ofs << std::endl; + + // Write nodes (only those in this group) + for (const std::string& nametag : nodes) { + if (factory_stats.find(nametag) != factory_stats.end()) { + FactoryCallStats& fstats = factory_stats[nametag]; + + ofs << " \"" << nametag << "\" ["; + ofs << "color=" << color; + ofs << ",shape=" << GetNodeShape(fstats.type); + + // Add timing information to label + double total_time = fstats.total_time_waiting_on + fstats.total_time_factory_active; + std::string time_str = MakeTimeString(total_time); + ofs << ",label=\"" << nametag << "\\n" << time_str << "\""; + ofs << "];" << std::endl; + } + } + ofs << std::endl; + + // Write edges (only those within this group) + for (auto& [link, stats] : call_links) { + std::string caller = MakeNametag(link.caller_name, link.caller_tag); + std::string callee = MakeNametag(link.callee_name, link.callee_tag); + + // Only include edges where both nodes are in this group + if (nodes.find(caller) != nodes.end() && nodes.find(callee) != nodes.end()) { + double total_time = stats.from_cache_ms + stats.from_source_ms + stats.from_factory_ms + stats.data_not_available_ms; + std::string time_str = MakeTimeString(total_time); + + ofs << " \"" << caller << "\" -> \"" << callee << "\""; + ofs << " [label=\"" << time_str << "\"];" << std::endl; + } + } + + ofs << "}" << std::endl; + ofs.close(); +} diff --git a/src/utilities/janadot/JEventProcessorJANADOT.h b/src/utilities/janadot/JEventProcessorJANADOT.h index 2a917a534e..9aae559f9e 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.h +++ b/src/utilities/janadot/JEventProcessorJANADOT.h @@ -96,6 +96,11 @@ class JEventProcessorJANADOT : public JEventProcessor { int max_edges_per_graph; std::string split_criteria; + // Group-related parameters + std::map> user_groups; // Group name -> list of factories + std::map user_group_colors; // Group name -> color + std::map nametag_to_group; // Nametag -> group name + // Helper methods std::string MakeTimeString(double time_in_ms); std::string MakeNametag(const std::string& name, const std::string& tag); @@ -117,6 +122,10 @@ class JEventProcessorJANADOT : public JEventProcessor { std::vector> SplitGraphBySize(); std::vector> SplitGraphByType(); std::map> SplitGraphByPlugin(); + std::map> SplitGraphByGroups(); + void WriteGroupGraphs(const std::map>& groups); + void WriteGroupDotFile(const std::string& group_name, const std::set& nodes); + void LoadGroupDefinitions(); std::string ExtractPluginName(const std::string& nametag); // Graph analysis methods diff --git a/src/utilities/janadot/README.md b/src/utilities/janadot/README.md index c188b98b77..4db7ea0612 100644 --- a/src/utilities/janadot/README.md +++ b/src/utilities/janadot/README.md @@ -22,7 +22,7 @@ The plugin supports several configuration parameters: - `janadot:enable_splitting` (default: true) - Enable splitting large graphs into multiple files - `janadot:max_nodes_per_graph` (default: 50) - Maximum number of nodes per graph when splitting - `janadot:max_edges_per_graph` (default: 100) - Maximum number of edges per graph when splitting -- `janadot:split_criteria` (default: "size") - Criteria for splitting graphs: size, components, type, plugin +- `janadot:split_criteria` (default: "plugin") - Criteria for splitting graphs: size, components, type, plugin, groups ## Plugin-based Splitting @@ -38,13 +38,37 @@ This generates: - `jana.hcal_endcap.dot` - HCAL endcap subsystem components - `jana.dot` - Overall inter-plugin connection summary +## Group-based Splitting + +To use user-defined group splitting based on the `.github/janadot.groups` file: + +```bash +eicrecon -Pplugins=janadot -Pjanadot:split_criteria=groups sim_file.root +``` + +This generates: +- `jana.EcalEndcapN.dot` - Components in the EcalEndcapN group +- `jana.HcalBarrel.dot` - Components in the HcalBarrel group +- `jana.Tracking.dot` - Components in the Tracking group +- `jana.dot` - Overall inter-group connection summary + +Groups can also be defined on the command line: + +```bash +eicrecon -Pplugins=janadot \ + -Pjanadot:split_criteria=groups \ + -Pjanadot:group:MyGroup="Factory1:Tag1,Factory2:Tag2,color_blue" \ + sim_file.root +``` + ## Output Files When splitting is disabled or graphs are small enough, a single DOT file is generated. When splitting is enabled and graphs are large, multiple files are created: - `jana_part001.dot`, `jana_part002.dot`, etc. - Individual graph parts (for size/components/type splitting) - `jana.plugin_name.dot` - Plugin-specific graphs (for plugin splitting) -- `jana.dot` - Main graph showing inter-plugin connections (for plugin splitting) +- `jana.GroupName.dot` - Group-specific graphs (for groups splitting) +- `jana.dot` - Main graph showing inter-plugin/inter-group connections (for plugin/groups splitting) - `jana_index.txt` - Index file explaining the split and how to process the files ## Generating Graphics From 971364290de082adaf9abccffa508cda1c0fbd77 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 22:11:41 +0000 Subject: [PATCH 20/29] Remove LoadGroupDefinitions functionality and restore command line group support Co-authored-by: wdconinc <4656391+wdconinc@users.noreply.github.com> --- .github/janadot.groups | 68 +++++++++---------- .github/workflows/linux-eic-shell.yml | 10 +-- .../janadot/JEventProcessorJANADOT.cc | 53 --------------- .../janadot/JEventProcessorJANADOT.h | 1 - 4 files changed, 39 insertions(+), 93 deletions(-) diff --git a/.github/janadot.groups b/.github/janadot.groups index c905949931..0bb389bdab 100644 --- a/.github/janadot.groups +++ b/.github/janadot.groups @@ -1,46 +1,46 @@ --PJANADOT:GROUP:EcalEndcapN=edm4hep::SimCalorimeterHit:EcalEndcapNHits,edm4hep::RawCalorimeterHit:EcalEndcapNRawHits,edm4eic::CalorimeterHit:EcalEndcapNRecHits,edm4eic::ProtoCluster:EcalEndcapNIslandProtoClusters,edm4eic::Cluster:EcalEndcapNClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapNClusterAssociations,edm4eic::ProtoCluster:EcalEndcapNTruthProtoClusters,edm4eic::Cluster:EcalEndcapNTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapNTruthClusterAssociations,color_blue --PJANADOT:GROUP:EcalBarrelScFi=edm4hep::SimCalorimeterHit:EcalBarrelScFiHits,edm4hep::RawCalorimeterHit:EcalBarrelScFiRawHits,edm4eic::CalorimeterHit:EcalBarrelScFiRecHits,edm4eic::ProtoCluster:EcalBarrelScFiProtoClusters,edm4eic::Cluster:EcalBarrelScFiClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelScFiClusterAssociations,color_red --PJANADOT:GROUP:EcalBarrelImaging=edm4hep::SimCalorimeterHit:EcalBarrelImagingHits,edm4hep::RawCalorimeterHit:EcalBarrelImagingRawHits,edm4eic::CalorimeterHit:EcalBarrelImagingRecHits,edm4eic::ProtoCluster:EcalBarrelImagingProtoClusters,edm4eic::Cluster:EcalBarrelImagingClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelImagingClusterAssociations,color_blue --PJANADOT:GROUP:EcalBarrel=edm4eic::Cluster:EcalBarrelClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelClusterAssociations,edm4eic::Cluster:EcalBarrelTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelTruthClusterAssociations,color_blue --PJANADOT:GROUP:Ecal=edm4eic::Cluster:EcalClusters,edm4eic::MCRecoClusterParticleAssociation:EcalClusterAssociations,color_blue --PJANADOT:GROUP:EcalEndcapP=edm4hep::SimCalorimeterHit:EcalEndcapPHits,edm4hep::RawCalorimeterHit:EcalEndcapPRawHits,edm4eic::CalorimeterHit:EcalEndcapPRecHits,edm4eic::ProtoCluster:EcalEndcapPIslandProtoClusters,edm4eic::Cluster:EcalEndcapPClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapPClusterAssociations,edm4eic::ProtoCluster:EcalEndcapPTruthProtoClusters,edm4eic::Cluster:EcalEndcapPTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapPTruthClusterAssociations,color_blue --PJANADOT:GROUP:B0Ecal=edm4hep::SimCalorimeterHit:B0ECalHits,edm4hep::RawCalorimeterHit:B0ECalRawHits,edm4eic::CalorimeterHit:B0ECalRecHits,edm4eic::ProtoCluster:B0ECalIslandProtoClusters,edm4eic::Cluster:B0ECalClusters,edm4eic::MCRecoClusterParticleAssociation:B0ECalClusterAssociations,color_blue --PJANADOT:GROUP:EcalFarForwardZDC=edm4hep::SimCalorimeterHit:EcalFarForwardZDCHits,edm4hep::RawCalorimeterHit:EcalFarForwardZDCRawHits,edm4eic::CalorimeterHit:EcalFarForwardZDCRecHits,edm4eic::CalorimeterHit:EcalFarForwardZDCSubcellHits,edm4eic::ProtoCluster:EcalFarForwardZDCImagingProtoClusters,edm4eic::ProtoCluster:EcalFarForwardZDCIslandProtoClusters,edm4eic::Cluster:EcalFarForwardZDCClusters,edm4eic::MCRecoClusterParticleAssociation:EcalFarForwardZDCClusterAssociations,edm4eic::ProtoCluster:EcalFarForwardZDCTruthProtoClusters,edm4eic::Cluster:EcalFarForwardZDCTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalFarForwardZDCTruthClusterAssociations,color_red --PJANADOT:GROUP:EcalLumiSpec=edm4hep::SimCalorimeterHit:EcalLumiSpecHits,edm4hep::RawCalorimeterHit:EcalLumiSpecRawHits,edm4eic::CalorimeterHit:EcalLumiSpecRecHits,edm4eic::CalorimeterHit:EcalLumiSpecSubcellHits,edm4eic::ProtoCluster:EcalLumiSpecImagingProtoClusters,edm4eic::ProtoCluster:EcalLumiSpecIslandProtoClusters,edm4eic::Cluster:EcalLumiSpecClusters,edm4eic::MCRecoClusterParticleAssociation:EcalLumiSpecClusterAssociations,edm4eic::ProtoCluster:EcalLumiSpecTruthProtoClusters,edm4eic::Cluster:EcalLumiSpecTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalLumiSpecTruthClusterAssociations,color_red +-Pjanadot:group:EcalEndcapN=edm4hep::SimCalorimeterHit:EcalEndcapNHits,edm4hep::RawCalorimeterHit:EcalEndcapNRawHits,edm4eic::CalorimeterHit:EcalEndcapNRecHits,edm4eic::ProtoCluster:EcalEndcapNIslandProtoClusters,edm4eic::Cluster:EcalEndcapNClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapNClusterAssociations,edm4eic::ProtoCluster:EcalEndcapNTruthProtoClusters,edm4eic::Cluster:EcalEndcapNTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapNTruthClusterAssociations,color_blue +-Pjanadot:group:EcalBarrelScFi=edm4hep::SimCalorimeterHit:EcalBarrelScFiHits,edm4hep::RawCalorimeterHit:EcalBarrelScFiRawHits,edm4eic::CalorimeterHit:EcalBarrelScFiRecHits,edm4eic::ProtoCluster:EcalBarrelScFiProtoClusters,edm4eic::Cluster:EcalBarrelScFiClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelScFiClusterAssociations,color_red +-Pjanadot:group:EcalBarrelImaging=edm4hep::SimCalorimeterHit:EcalBarrelImagingHits,edm4hep::RawCalorimeterHit:EcalBarrelImagingRawHits,edm4eic::CalorimeterHit:EcalBarrelImagingRecHits,edm4eic::ProtoCluster:EcalBarrelImagingProtoClusters,edm4eic::Cluster:EcalBarrelImagingClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelImagingClusterAssociations,color_blue +-Pjanadot:group:EcalBarrel=edm4eic::Cluster:EcalBarrelClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelClusterAssociations,edm4eic::Cluster:EcalBarrelTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalBarrelTruthClusterAssociations,color_blue +-Pjanadot:group:Ecal=edm4eic::Cluster:EcalClusters,edm4eic::MCRecoClusterParticleAssociation:EcalClusterAssociations,color_blue +-Pjanadot:group:EcalEndcapP=edm4hep::SimCalorimeterHit:EcalEndcapPHits,edm4hep::RawCalorimeterHit:EcalEndcapPRawHits,edm4eic::CalorimeterHit:EcalEndcapPRecHits,edm4eic::ProtoCluster:EcalEndcapPIslandProtoClusters,edm4eic::Cluster:EcalEndcapPClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapPClusterAssociations,edm4eic::ProtoCluster:EcalEndcapPTruthProtoClusters,edm4eic::Cluster:EcalEndcapPTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalEndcapPTruthClusterAssociations,color_blue +-Pjanadot:group:B0Ecal=edm4hep::SimCalorimeterHit:B0ECalHits,edm4hep::RawCalorimeterHit:B0ECalRawHits,edm4eic::CalorimeterHit:B0ECalRecHits,edm4eic::ProtoCluster:B0ECalIslandProtoClusters,edm4eic::Cluster:B0ECalClusters,edm4eic::MCRecoClusterParticleAssociation:B0ECalClusterAssociations,color_blue +-Pjanadot:group:EcalFarForwardZDC=edm4hep::SimCalorimeterHit:EcalFarForwardZDCHits,edm4hep::RawCalorimeterHit:EcalFarForwardZDCRawHits,edm4eic::CalorimeterHit:EcalFarForwardZDCRecHits,edm4eic::CalorimeterHit:EcalFarForwardZDCSubcellHits,edm4eic::ProtoCluster:EcalFarForwardZDCImagingProtoClusters,edm4eic::ProtoCluster:EcalFarForwardZDCIslandProtoClusters,edm4eic::Cluster:EcalFarForwardZDCClusters,edm4eic::MCRecoClusterParticleAssociation:EcalFarForwardZDCClusterAssociations,edm4eic::ProtoCluster:EcalFarForwardZDCTruthProtoClusters,edm4eic::Cluster:EcalFarForwardZDCTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalFarForwardZDCTruthClusterAssociations,color_red +-Pjanadot:group:EcalLumiSpec=edm4hep::SimCalorimeterHit:EcalLumiSpecHits,edm4hep::RawCalorimeterHit:EcalLumiSpecRawHits,edm4eic::CalorimeterHit:EcalLumiSpecRecHits,edm4eic::CalorimeterHit:EcalLumiSpecSubcellHits,edm4eic::ProtoCluster:EcalLumiSpecImagingProtoClusters,edm4eic::ProtoCluster:EcalLumiSpecIslandProtoClusters,edm4eic::Cluster:EcalLumiSpecClusters,edm4eic::MCRecoClusterParticleAssociation:EcalLumiSpecClusterAssociations,edm4eic::ProtoCluster:EcalLumiSpecTruthProtoClusters,edm4eic::Cluster:EcalLumiSpecTruthClusters,edm4eic::MCRecoClusterParticleAssociation:EcalLumiSpecTruthClusterAssociations,color_red --PJANADOT:GROUP:HcalEndcapN=edm4hep::SimCalorimeterHit:HcalEndcapNHits,edm4hep::RawCalorimeterHit:HcalEndcapNRawHits,edm4eic::CalorimeterHit:HcalEndcapNRecHits,edm4eic::CalorimeterHit:HcalEndcapNMergedHits,edm4eic::ProtoCluster:HcalEndcapNIslandProtoClusters,edm4eic::Cluster:HcalEndcapNClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapNClusterAssociations,edm4eic::ProtoCluster:HcalEndcapNTruthProtoClusters,edm4eic::Cluster:HcalEndcapNTruthClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapNTruthClusterAssociations,color_red --PJANADOT:GROUP:HcalBarrel=edm4hep::SimCalorimeterHit:HcalBarrelHits,edm4hep::RawCalorimeterHit:HcalBarrelRawHits,edm4eic::CalorimeterHit:HcalBarrelRecHits,edm4eic::ProtoCluster:HcalBarrelIslandProtoClusters,edm4eic::Cluster:HcalBarrelClusters,edm4eic::MCRecoClusterParticleAssociation:HcalBarrelClusterAssociations,edm4eic::ProtoCluster:HcalBarrelTruthProtoClusters,edm4eic::Cluster:HcalBarrelTruthClusters,edm4eic::MCRecoClusterParticleAssociation:HcalBarrelTruthClusterAssociations,color_red --PJANADOT:GROUP:HcalEndcapP=edm4hep::SimCalorimeterHit:HcalEndcapPHits,edm4hep::RawCalorimeterHit:HcalEndcapPRawHits,edm4eic::CalorimeterHit:HcalEndcapPRecHits,edm4eic::ProtoCluster:HcalEndcapPIslandProtoClusters,edm4eic::Cluster:HcalEndcapPClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapPClusterAssociations,color_red --PJANADOT:GROUP:LFHCAL=edm4hep::SimCalorimeterHit:LFHCALHits,edm4hep::RawCalorimeterHit:LFHCALRawHits,edm4eic::CalorimeterHit:LFHCALRecHits,edm4eic::ProtoCluster:LFHCALIslandProtoClusters,edm4eic::Cluster:LFHCALClusters,edm4eic::MCRecoClusterParticleAssociation:LFHCALClusterAssociations,color_red --PJANADOT:GROUP:HcalEndcapPInsert=edm4hep::SimCalorimeterHit:HcalEndcapPInsertHits,edm4hep::RawCalorimeterHit:HcalEndcapPInsertRawHits,edm4eic::CalorimeterHit:HcalEndcapPInsertRecHits,edm4eic::CalorimeterHit:HcalEndcapPInsertMergedHits,edm4eic::ProtoCluster:HcalEndcapPInsertIslandProtoClusters,edm4eic::Cluster:HcalEndcapPInsertClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapPInsertClusterAssociations,color_red --PJANADOT:GROUP:HcalFarForwardZDC=edm4hep::SimCalorimeterHit:HcalFarForwardZDCHits,edm4hep::RawCalorimeterHit:HcalFarForwardZDCRawHits,edm4eic::CalorimeterHit:HcalFarForwardZDCRecHits,edm4eic::CalorimeterHit:HcalFarForwardZDCSubcellHits,edm4eic::ProtoCluster:HcalFarForwardZDCImagingProtoClusters,edm4eic::ProtoCluster:HcalFarForwardZDCIslandProtoClusters,edm4eic::Cluster:HcalFarForwardZDCClusters,edm4eic::MCRecoClusterParticleAssociation:HcalFarForwardZDCClusterAssociations,edm4eic::ProtoCluster:HcalFarForwardZDCTruthProtoClusters,edm4eic::Cluster:HcalFarForwardZDCTruthClusters,edm4eic::MCRecoClusterParticleAssociation:HcalFarForwardZDCTruthClusterAssociations,edm4eic::ProtoCluster:HcalFarForwardZDCIslandProtoClustersBaseline,edm4eic::Cluster:HcalFarForwardZDCClustersBaseline,edm4eic::MCRecoClusterParticleAssociation:HcalFarForwardZDCClusterAssociationsBaseline,color_red --PJANADOT:GROUP:RICHEndcapN=edm4hep::SimTrackerHit:RICHEndcapNHits,edm4eic::RawTrackerHit:RICHEndcapNRawHits,edm4eic::MCRecoTrackerHitAssociation:RICHEndcapNRawHitsAssociations,color_green --PJANADOT:GROUP:DIRC=edm4hep::SimTrackerHit:DIRCBarHits,edm4eic::RawTrackerHit:DIRCRawHits,color_green +-Pjanadot:group:HcalEndcapN=edm4hep::SimCalorimeterHit:HcalEndcapNHits,edm4hep::RawCalorimeterHit:HcalEndcapNRawHits,edm4eic::CalorimeterHit:HcalEndcapNRecHits,edm4eic::CalorimeterHit:HcalEndcapNMergedHits,edm4eic::ProtoCluster:HcalEndcapNIslandProtoClusters,edm4eic::Cluster:HcalEndcapNClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapNClusterAssociations,edm4eic::ProtoCluster:HcalEndcapNTruthProtoClusters,edm4eic::Cluster:HcalEndcapNTruthClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapNTruthClusterAssociations,color_red +-Pjanadot:group:HcalBarrel=edm4hep::SimCalorimeterHit:HcalBarrelHits,edm4hep::RawCalorimeterHit:HcalBarrelRawHits,edm4eic::CalorimeterHit:HcalBarrelRecHits,edm4eic::ProtoCluster:HcalBarrelIslandProtoClusters,edm4eic::Cluster:HcalBarrelClusters,edm4eic::MCRecoClusterParticleAssociation:HcalBarrelClusterAssociations,edm4eic::ProtoCluster:HcalBarrelTruthProtoClusters,edm4eic::Cluster:HcalBarrelTruthClusters,edm4eic::MCRecoClusterParticleAssociation:HcalBarrelTruthClusterAssociations,color_red +-Pjanadot:group:HcalEndcapP=edm4hep::SimCalorimeterHit:HcalEndcapPHits,edm4hep::RawCalorimeterHit:HcalEndcapPRawHits,edm4eic::CalorimeterHit:HcalEndcapPRecHits,edm4eic::ProtoCluster:HcalEndcapPIslandProtoClusters,edm4eic::Cluster:HcalEndcapPClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapPClusterAssociations,color_red +-Pjanadot:group:LFHCAL=edm4hep::SimCalorimeterHit:LFHCALHits,edm4hep::RawCalorimeterHit:LFHCALRawHits,edm4eic::CalorimeterHit:LFHCALRecHits,edm4eic::ProtoCluster:LFHCALIslandProtoClusters,edm4eic::Cluster:LFHCALClusters,edm4eic::MCRecoClusterParticleAssociation:LFHCALClusterAssociations,color_red +-Pjanadot:group:HcalEndcapPInsert=edm4hep::SimCalorimeterHit:HcalEndcapPInsertHits,edm4hep::RawCalorimeterHit:HcalEndcapPInsertRawHits,edm4eic::CalorimeterHit:HcalEndcapPInsertRecHits,edm4eic::CalorimeterHit:HcalEndcapPInsertMergedHits,edm4eic::ProtoCluster:HcalEndcapPInsertIslandProtoClusters,edm4eic::Cluster:HcalEndcapPInsertClusters,edm4eic::MCRecoClusterParticleAssociation:HcalEndcapPInsertClusterAssociations,color_red +-Pjanadot:group:HcalFarForwardZDC=edm4hep::SimCalorimeterHit:HcalFarForwardZDCHits,edm4hep::RawCalorimeterHit:HcalFarForwardZDCRawHits,edm4eic::CalorimeterHit:HcalFarForwardZDCRecHits,edm4eic::CalorimeterHit:HcalFarForwardZDCSubcellHits,edm4eic::ProtoCluster:HcalFarForwardZDCImagingProtoClusters,edm4eic::ProtoCluster:HcalFarForwardZDCIslandProtoClusters,edm4eic::Cluster:HcalFarForwardZDCClusters,edm4eic::MCRecoClusterParticleAssociation:HcalFarForwardZDCClusterAssociations,edm4eic::ProtoCluster:HcalFarForwardZDCTruthProtoClusters,edm4eic::Cluster:HcalFarForwardZDCTruthClusters,edm4eic::MCRecoClusterParticleAssociation:HcalFarForwardZDCTruthClusterAssociations,edm4eic::ProtoCluster:HcalFarForwardZDCIslandProtoClustersBaseline,edm4eic::Cluster:HcalFarForwardZDCClustersBaseline,edm4eic::MCRecoClusterParticleAssociation:HcalFarForwardZDCClusterAssociationsBaseline,color_red +-Pjanadot:group:RICHEndcapN=edm4hep::SimTrackerHit:RICHEndcapNHits,edm4eic::RawTrackerHit:RICHEndcapNRawHits,edm4eic::MCRecoTrackerHitAssociation:RICHEndcapNRawHitsAssociations,color_green +-Pjanadot:group:DIRC=edm4hep::SimTrackerHit:DIRCBarHits,edm4eic::RawTrackerHit:DIRCRawHits,color_green --PJANADOT:GROUP:Tracking=edm4eic::TrackerHit:CentralTrackingRecHits,edm4eic::TrackParameters:CentralTrackSeedingResults,edm4eic::Measurement2D:CentralTrackerMeasurements,edm4eic::TrackParameters:CentralCKFSeededTrackParameters,ActsExamples::Trajectories:CentralCKFActsTrajectories,Acts::TrackContainer:CentralCKFActsTracks,edm4eic::TrackParameters:CentralCKFTrackParameters,edm4eic::Track:CentralCKFTracks +-Pjanadot:group:Tracking=edm4eic::TrackerHit:CentralTrackingRecHits,edm4eic::TrackParameters:CentralTrackSeedingResults,edm4eic::Measurement2D:CentralTrackerMeasurements,edm4eic::TrackParameters:CentralCKFSeededTrackParameters,ActsExamples::Trajectories:CentralCKFActsTrajectories,Acts::TrackContainer:CentralCKFActsTracks,edm4eic::TrackParameters:CentralCKFTrackParameters,edm4eic::Track:CentralCKFTracks --PJANADOT:GROUP:B0Tracker=edm4hep::SimTrackerHit:B0TrackerHits,edm4eic::TrackerHit:B0TrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:B0TrackerRecHitsAssociations,color_green +-Pjanadot:group:B0Tracker=edm4hep::SimTrackerHit:B0TrackerHits,edm4eic::TrackerHit:B0TrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:B0TrackerRecHitsAssociations,color_green --PJANADOT:GROUP:TrackerBarrel=edm4hep::SimTrackerHit:SiBarrelHits,edm4eic::TrackerHit:SiBarrelTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:SiBarrelTrackerRecHitsAssociations,color_green --PJANADOT:GROUP:TrackerEndcapN=edm4hep::SimTrackerHit:SiEndcapNHits,edm4eic::TrackerHit:SiEndcapNTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:SiEndcapNTrackerRecHitsAssociations,color_green --PJANADOT:GROUP:TrackerEndcapP=edm4hep::SimTrackerHit:SiEndcapPHits,edm4eic::TrackerHit:SiEndcapPTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:SiEndcapPTrackerRecHitsAssociations,color_green +-Pjanadot:group:TrackerBarrel=edm4hep::SimTrackerHit:SiBarrelHits,edm4eic::TrackerHit:SiBarrelTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:SiBarrelTrackerRecHitsAssociations,color_green +-Pjanadot:group:TrackerEndcapN=edm4hep::SimTrackerHit:SiEndcapNHits,edm4eic::TrackerHit:SiEndcapNTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:SiEndcapNTrackerRecHitsAssociations,color_green +-Pjanadot:group:TrackerEndcapP=edm4hep::SimTrackerHit:SiEndcapPHits,edm4eic::TrackerHit:SiEndcapPTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:SiEndcapPTrackerRecHitsAssociations,color_green --PJANADOT:GROUP:GEMEndcapN=edm4hep::SimTrackerHit:GEMEndcapNHits,edm4eic::TrackerHit:GEMEndcapNTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:GEMEndcapNTrackerRecHitsAssociations,color_green --PJANADOT:GROUP:MPGDBarrel=edm4hep::SimTrackerHit:MPGDBarrelHits,edm4eic::TrackerHit:MPGDBarrelTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:MPGDBarrelTrackerRecHitsAssociations,color_green +-Pjanadot:group:GEMEndcapN=edm4hep::SimTrackerHit:GEMEndcapNHits,edm4eic::TrackerHit:GEMEndcapNTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:GEMEndcapNTrackerRecHitsAssociations,color_green +-Pjanadot:group:MPGDBarrel=edm4hep::SimTrackerHit:MPGDBarrelHits,edm4eic::TrackerHit:MPGDBarrelTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:MPGDBarrelTrackerRecHitsAssociations,color_green --PJANADOT:GROUP:TOFBarrel=edm4hep::SimTrackerHit:TOFBarrelHits,edm4eic::TrackerHit:TOFBarrelRecHits,edm4eic::MCRecoTrackerHitAssociation:TOFBarrelRecHitsAssociations,color_green --PJANADOT:GROUP:TOFEndcapN=edm4hep::SimTrackerHit:TOFEndcapHits,edm4eic::TrackerHit:TOFEndcapRecHits,edm4eic::MCRecoTrackerHitAssociation:TOFEndcapRecHitsAssociations,color_green +-Pjanadot:group:TOFBarrel=edm4hep::SimTrackerHit:TOFBarrelHits,edm4eic::TrackerHit:TOFBarrelRecHits,edm4eic::MCRecoTrackerHitAssociation:TOFBarrelRecHitsAssociations,color_green +-Pjanadot:group:TOFEndcapN=edm4hep::SimTrackerHit:TOFEndcapHits,edm4eic::TrackerHit:TOFEndcapRecHits,edm4eic::MCRecoTrackerHitAssociation:TOFEndcapRecHitsAssociations,color_green --PJANADOT:GROUP:TruthTracks=edm4eic::Track:TruthTracks,color_lightblue --PJANADOT:GROUP:Vertices=edm4eic::Vertex:CentralCKFVertices,edm4eic::Vertex:TruthPrimaryVertices,color_purple +-Pjanadot:group:TruthTracks=edm4eic::Track:TruthTracks,color_lightblue +-Pjanadot:group:Vertices=edm4eic::Vertex:CentralCKFVertices,edm4eic::Vertex:TruthPrimaryVertices,color_purple --PJANADOT:GROUP:MCParticles=edm4hep::MCParticle:MCParticles,color_orange --PJANADOT:GROUP:InputOutput=edm4hep::EventHeader:EventHeader,color_gray +-Pjanadot:group:MCParticles=edm4hep::MCParticle:MCParticles,color_orange +-Pjanadot:group:InputOutput=edm4hep::EventHeader:EventHeader,color_gray --PJANADOT:GROUP:Jets=edm4eic::InclusiveKinematicsDA:InclusiveKinematicsDA,edm4eic::InclusiveKinematicsElectron:InclusiveKinematicsElectron,edm4eic::InclusiveKinematicsJB:InclusiveKinematicsJB,edm4eic::InclusiveKinematicsSigma:InclusiveKinematicsSigma,edm4eic::InclusiveKinematicseSigma:InclusiveKinematicseSigma,edm4eic::ReconstructedParticle:ReconstructedChargedParticles,edm4eic::ReconstructedParticle:ReconstructedNeutralParticles,edm4eic::ReconstructedParticle:ReconstructedParticles,edm4eic::MCRecoParticleAssociation:ReconstructedChargedParticleAssociations,edm4eic::MCRecoParticleAssociation:ReconstructedNeutralParticleAssociations,edm4eic::MCRecoParticleAssociation:ReconstructedParticleAssociations,edm4eic::ReconstructedParticle:ScatteredElectronsTruth,edm4eic::ReconstructedParticle:GeneratedJets,edm4eic::ReconstructedParticle:ReconstructedJets,edm4eic::MCRecoParticleAssociation:GeneratedChargedJetsAssociations,edm4eic::MCRecoParticleAssociation:ReconstructedJetsAssociations,color_cyan +-Pjanadot:group:Jets=edm4eic::InclusiveKinematicsDA:InclusiveKinematicsDA,edm4eic::InclusiveKinematicsElectron:InclusiveKinematicsElectron,edm4eic::InclusiveKinematicsJB:InclusiveKinematicsJB,edm4eic::InclusiveKinematicsSigma:InclusiveKinematicsSigma,edm4eic::InclusiveKinematicseSigma:InclusiveKinematicseSigma,edm4eic::ReconstructedParticle:ReconstructedChargedParticles,edm4eic::ReconstructedParticle:ReconstructedNeutralParticles,edm4eic::ReconstructedParticle:ReconstructedParticles,edm4eic::MCRecoParticleAssociation:ReconstructedChargedParticleAssociations,edm4eic::MCRecoParticleAssociation:ReconstructedNeutralParticleAssociations,edm4eic::MCRecoParticleAssociation:ReconstructedParticleAssociations,edm4eic::ReconstructedParticle:ScatteredElectronsTruth,edm4eic::ReconstructedParticle:GeneratedJets,edm4eic::ReconstructedParticle:ReconstructedJets,edm4eic::MCRecoParticleAssociation:GeneratedChargedJetsAssociations,edm4eic::MCRecoParticleAssociation:ReconstructedJetsAssociations,color_cyan --PJANADOT:GROUP:GeneratedParticles=edm4hep::GeneratedParticles,color_lightyellow +-Pjanadot:group:GeneratedParticles=edm4hep::GeneratedParticles,color_lightyellow --PJANADOT:GROUP:FarForward=edm4hep::SimTrackerHit:ForwardOffMTrackerHits,edm4eic::TrackerHit:ForwardOffMTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:ForwardOffMTrackerRecHitsAssociations,edm4hep::SimTrackerHit:ForwardRomanPotHits,edm4eic::TrackerHit:ForwardRomanPotRecHits,edm4eic::MCRecoTrackerHitAssociation:ForwardRomanPotRecHitsAssociations,color_brown +-Pjanadot:group:FarForward=edm4hep::SimTrackerHit:ForwardOffMTrackerHits,edm4eic::TrackerHit:ForwardOffMTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:ForwardOffMTrackerRecHitsAssociations,edm4hep::SimTrackerHit:ForwardRomanPotHits,edm4eic::TrackerHit:ForwardRomanPotRecHits,edm4eic::MCRecoTrackerHitAssociation:ForwardRomanPotRecHitsAssociations,color_brown --PJANADOT:GROUP:ParticleID=edm4eic::CherenkovParticleID:PFRICH_PID,edm4eic::CherenkovParticleIDHypothesis:PFRICH_PID_Hypothesis,edm4eic::dRICHAerogelIrtCherenkovParticleID:dRICHAerogelIrtCherenkovParticleID,edm4eic::dRICHGasIrtCherenkovParticleID:dRICHGasIrtCherenkovParticleID,color_pink \ No newline at end of file +-Pjanadot:group:ParticleID=edm4eic::CherenkovParticleID:PFRICH_PID,edm4eic::CherenkovParticleIDHypothesis:PFRICH_PID_Hypothesis,edm4eic::dRICHAerogelIrtCherenkovParticleID:dRICHAerogelIrtCherenkovParticleID,edm4eic::dRICHGasIrtCherenkovParticleID:dRICHGasIrtCherenkovParticleID,color_pink \ No newline at end of file diff --git a/.github/workflows/linux-eic-shell.yml b/.github/workflows/linux-eic-shell.yml index fc8ab03ab8..61fffccb6d 100644 --- a/.github/workflows/linux-eic-shell.yml +++ b/.github/workflows/linux-eic-shell.yml @@ -549,7 +549,7 @@ jobs: export DETECTOR_CONFIG=${DETECTOR}_${{ matrix.detector_config }} export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins${JANA_PLUGIN_PATH:+:${JANA_PLUGIN_PATH}} - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_collections=EventHeader,MCParticles,EcalBarrelScFiRawHits,EcalBarrelImagingRawHits -Ppodio:output_file=two_stage_raw_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,janadot -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_collections=EventHeader,MCParticles,EcalBarrelScFiRawHits,EcalBarrelImagingRawHits -Ppodio:output_file=two_stage_raw_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,janadot -Pjanadot:split_criteria=groups $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json - name: Upload digitization output uses: actions/upload-artifact@v4 with: @@ -565,7 +565,7 @@ jobs: export DETECTOR_CONFIG=${DETECTOR}_${{ matrix.detector_config }} export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins${JANA_PLUGIN_PATH:+:${JANA_PLUGIN_PATH}} - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_collections=EcalBarrelClusters,EcalBarrelClusterAssociations -Ppodio:output_file=two_stage_rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root two_stage_raw_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root -Pplugins=dump_flags,janadot -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_collections=EcalBarrelClusters,EcalBarrelClusterAssociations -Ppodio:output_file=two_stage_rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root two_stage_raw_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root -Pplugins=dump_flags,janadot -Pjanadot:split_criteria=groups $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json - name: Upload reconstruction output uses: actions/upload-artifact@v4 with: @@ -709,7 +709,7 @@ jobs: export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins prmon --json-summary rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.prmon.json -- \ - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,janadot,janatop -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4hep.root -Pplugins=dump_flags,janadot,janatop -Pjanadot:split_criteria=groups $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json - uses: actions/upload-artifact@v4 with: name: rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.edm4eic.root @@ -824,7 +824,7 @@ jobs: export DETECTOR_CONFIG=${DETECTOR}_${{ matrix.detector_config }} export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4hep.root -Ppodio:output_collections=EcalLumiSpecRawHits,EcalLumiSpecRecHits,EcalLumiSpecClusters,EcalLumiSpecClusterAssociations -PLUMISPECCAL:EcalLumiSpecIslandProtoClusters:splitCluster=1 -Pplugins=dump_flags,janadot,janatop -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{env.JANA_OPTIONS_GUN}} -Ppodio:output_file=rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root sim_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4hep.root -Ppodio:output_collections=EcalLumiSpecRawHits,EcalLumiSpecRecHits,EcalLumiSpecClusters,EcalLumiSpecClusterAssociations -PLUMISPECCAL:EcalLumiSpecIslandProtoClusters:splitCluster=1 -Pplugins=dump_flags,janadot,janatop -Pjanadot:split_criteria=groups $(<${{ github.workspace }}/.github/janadot.groups) -Pdump_flags:json=${{ matrix.particle }}_${{ matrix.detector_config }}_flags.json - uses: actions/upload-artifact@v4 with: name: rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.edm4eic.root @@ -936,7 +936,7 @@ jobs: export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins prmon --json-summary rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.prmon.json -- \ - $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{matrix.nthreads > 1 && format('-Pnthreads={0}', matrix.nthreads) || ''}} -Ppodio:output_file=rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root sim_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}.edm4hep.root -Pacts:WriteObj=true -Pacts:WritePly=true -Pplugins=janadot,janatop + $PWD/install/bin/eicrecon ${{env.JANA_OPTIONS}} ${{matrix.nthreads > 1 && format('-Pnthreads={0}', matrix.nthreads) || ''}} -Ppodio:output_file=rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root sim_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}.edm4hep.root -Pacts:WriteObj=true -Pacts:WritePly=true -Pplugins=janadot,janatop -Pjanadot:split_criteria=groups $(<${{ github.workspace }}/.github/janadot.groups) - uses: actions/upload-artifact@v4 with: name: rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.edm4eic.root diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index 92a90897e8..e4dadb8713 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -47,9 +47,6 @@ void JEventProcessorJANADOT::Init() { params->SetDefaultParameter("janadot:split_criteria", split_criteria, "Criteria for splitting graphs: size, components, type, plugin, groups"); - // Load group definitions if they exist - LoadGroupDefinitions(); - // Also check for JANADOT:GROUP parameters (command line group definitions) auto parameter_keys = params->GetParameterNames(); for (const auto& key : parameter_keys) { @@ -1079,57 +1076,7 @@ std::string JEventProcessorJANADOT::ExtractPluginName(const std::string& nametag return "misc"; } -void JEventProcessorJANADOT::LoadGroupDefinitions() { - // Load group definitions from .github/janadot.groups file - std::ifstream file(".github/janadot.groups"); - if (!file.is_open()) { - std::cout << "Warning: Could not open .github/janadot.groups file. Group splitting may not work properly." << std::endl; - return; - } - - std::string line; - while (std::getline(file, line)) { - // Skip empty lines and comments - if (line.empty() || line[0] == '#') { - continue; - } - - // Parse lines of format: -PJANADOT:GROUP:GroupName=factory1,factory2,...,color - if (line.find("-PJANADOT:GROUP:") == 0) { - size_t group_start = line.find("GROUP:") + 6; - size_t equals_pos = line.find("=", group_start); - if (equals_pos == std::string::npos) continue; - - std::string group_name = line.substr(group_start, equals_pos - group_start); - std::string factories_str = line.substr(equals_pos + 1); - // Parse comma-separated list of factories and color - std::vector factories; - std::string color = "lightblue"; // default color - - std::stringstream ss(factories_str); - std::string item; - while (std::getline(ss, item, ',')) { - if (item.find("color_") == 0) { - color = item.substr(6); // remove "color_" prefix - } else if (!item.empty()) { - factories.push_back(item); - } - } - - user_groups[group_name] = factories; - user_group_colors[group_name] = color; - - // Build nametag to group mapping - for (const auto& factory : factories) { - nametag_to_group[factory] = group_name; - } - } - } - file.close(); - - std::cout << "Loaded " << user_groups.size() << " group definitions from .github/janadot.groups" << std::endl; -} std::map> JEventProcessorJANADOT::SplitGraphByGroups() { std::map> groups; diff --git a/src/utilities/janadot/JEventProcessorJANADOT.h b/src/utilities/janadot/JEventProcessorJANADOT.h index 9aae559f9e..755f8d157d 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.h +++ b/src/utilities/janadot/JEventProcessorJANADOT.h @@ -125,7 +125,6 @@ class JEventProcessorJANADOT : public JEventProcessor { std::map> SplitGraphByGroups(); void WriteGroupGraphs(const std::map>& groups); void WriteGroupDotFile(const std::string& group_name, const std::set& nodes); - void LoadGroupDefinitions(); std::string ExtractPluginName(const std::string& nametag); // Graph analysis methods From d272a06a400616d0834bcf5b3b932ed828c5e1e2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Aug 2025 22:13:45 +0000 Subject: [PATCH 21/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .github/janadot.groups | 2 +- .../janadot/JEventProcessorJANADOT.cc | 38 ++++++++++--------- .../janadot/JEventProcessorJANADOT.h | 2 +- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/.github/janadot.groups b/.github/janadot.groups index 0bb389bdab..b84d94c095 100644 --- a/.github/janadot.groups +++ b/.github/janadot.groups @@ -43,4 +43,4 @@ -Pjanadot:group:FarForward=edm4hep::SimTrackerHit:ForwardOffMTrackerHits,edm4eic::TrackerHit:ForwardOffMTrackerRecHits,edm4eic::MCRecoTrackerHitAssociation:ForwardOffMTrackerRecHitsAssociations,edm4hep::SimTrackerHit:ForwardRomanPotHits,edm4eic::TrackerHit:ForwardRomanPotRecHits,edm4eic::MCRecoTrackerHitAssociation:ForwardRomanPotRecHitsAssociations,color_brown --Pjanadot:group:ParticleID=edm4eic::CherenkovParticleID:PFRICH_PID,edm4eic::CherenkovParticleIDHypothesis:PFRICH_PID_Hypothesis,edm4eic::dRICHAerogelIrtCherenkovParticleID:dRICHAerogelIrtCherenkovParticleID,edm4eic::dRICHGasIrtCherenkovParticleID:dRICHGasIrtCherenkovParticleID,color_pink \ No newline at end of file +-Pjanadot:group:ParticleID=edm4eic::CherenkovParticleID:PFRICH_PID,edm4eic::CherenkovParticleIDHypothesis:PFRICH_PID_Hypothesis,edm4eic::dRICHAerogelIrtCherenkovParticleID:dRICHAerogelIrtCherenkovParticleID,edm4eic::dRICHGasIrtCherenkovParticleID:dRICHGasIrtCherenkovParticleID,color_pink diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index e4dadb8713..e60d987bf0 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -44,8 +44,9 @@ void JEventProcessorJANADOT::Init() { "Maximum number of edges per graph when splitting"); split_criteria = "plugin"; - params->SetDefaultParameter("janadot:split_criteria", split_criteria, - "Criteria for splitting graphs: size, components, type, plugin, groups"); + params->SetDefaultParameter( + "janadot:split_criteria", split_criteria, + "Criteria for splitting graphs: size, components, type, plugin, groups"); // Also check for JANADOT:GROUP parameters (command line group definitions) auto parameter_keys = params->GetParameterNames(); @@ -54,11 +55,11 @@ void JEventProcessorJANADOT::Init() { std::string group_name = key.substr(13); // Remove "janadot:group:" prefix std::string group_definition; params->GetParameter(key, group_definition); - + // Parse command line group definition similar to file format std::vector factories; std::string color = "lightblue"; // default color - + std::stringstream ss(group_definition); std::string item; while (std::getline(ss, item, ',')) { @@ -69,7 +70,7 @@ void JEventProcessorJANADOT::Init() { } } - user_groups[group_name] = factories; + user_groups[group_name] = factories; user_group_colors[group_name] = color; // Build nametag to group mapping @@ -1076,15 +1077,13 @@ std::string JEventProcessorJANADOT::ExtractPluginName(const std::string& nametag return "misc"; } - - std::map> JEventProcessorJANADOT::SplitGraphByGroups() { std::map> groups; // Group nodes based on user-defined groups for (auto& [nametag, fstats] : factory_stats) { std::string group_name = "Ungrouped"; - + // Check if this factory is in any user-defined group if (nametag_to_group.find(nametag) != nametag_to_group.end()) { group_name = nametag_to_group[nametag]; @@ -1096,7 +1095,8 @@ std::map> JEventProcessorJANADOT::SplitGraphB return groups; } -void JEventProcessorJANADOT::WriteGroupGraphs(const std::map>& groups) { +void JEventProcessorJANADOT::WriteGroupGraphs( + const std::map>& groups) { std::cout << "Splitting graph into " << groups.size() << " group-based subgraphs" << std::endl; for (auto& [group_name, nodes] : groups) { @@ -1107,14 +1107,15 @@ void JEventProcessorJANADOT::WriteGroupGraphs(const std::map& nodes) { +void JEventProcessorJANADOT::WriteGroupDotFile(const std::string& group_name, + const std::set& nodes) { // Create filename like jana.GroupName.dot std::string base_filename = output_filename; - size_t dot_pos = base_filename.find_last_of('.'); + size_t dot_pos = base_filename.find_last_of('.'); if (dot_pos != std::string::npos) { base_filename = base_filename.substr(0, dot_pos); } - + std::string filename = base_filename + "." + group_name + ".dot"; std::ofstream ofs(filename); @@ -1138,13 +1139,13 @@ void JEventProcessorJANADOT::WriteGroupDotFile(const std::string& group_name, co for (const std::string& nametag : nodes) { if (factory_stats.find(nametag) != factory_stats.end()) { FactoryCallStats& fstats = factory_stats[nametag]; - + ofs << " \"" << nametag << "\" ["; ofs << "color=" << color; ofs << ",shape=" << GetNodeShape(fstats.type); - + // Add timing information to label - double total_time = fstats.total_time_waiting_on + fstats.total_time_factory_active; + double total_time = fstats.total_time_waiting_on + fstats.total_time_factory_active; std::string time_str = MakeTimeString(total_time); ofs << ",label=\"" << nametag << "\\n" << time_str << "\""; ofs << "];" << std::endl; @@ -1156,12 +1157,13 @@ void JEventProcessorJANADOT::WriteGroupDotFile(const std::string& group_name, co for (auto& [link, stats] : call_links) { std::string caller = MakeNametag(link.caller_name, link.caller_tag); std::string callee = MakeNametag(link.callee_name, link.callee_tag); - + // Only include edges where both nodes are in this group if (nodes.find(caller) != nodes.end() && nodes.find(callee) != nodes.end()) { - double total_time = stats.from_cache_ms + stats.from_source_ms + stats.from_factory_ms + stats.data_not_available_ms; + double total_time = stats.from_cache_ms + stats.from_source_ms + stats.from_factory_ms + + stats.data_not_available_ms; std::string time_str = MakeTimeString(total_time); - + ofs << " \"" << caller << "\" -> \"" << callee << "\""; ofs << " [label=\"" << time_str << "\"];" << std::endl; } diff --git a/src/utilities/janadot/JEventProcessorJANADOT.h b/src/utilities/janadot/JEventProcessorJANADOT.h index 755f8d157d..77db63e164 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.h +++ b/src/utilities/janadot/JEventProcessorJANADOT.h @@ -97,7 +97,7 @@ class JEventProcessorJANADOT : public JEventProcessor { std::string split_criteria; // Group-related parameters - std::map> user_groups; // Group name -> list of factories + std::map> user_groups; // Group name -> list of factories std::map user_group_colors; // Group name -> color std::map nametag_to_group; // Nametag -> group name From fc644a984e60c37f30ca2b557fd97110abe672b7 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 23 Aug 2025 17:19:04 -0500 Subject: [PATCH 22/29] Refactor dot file renaming and conversion logic --- .github/workflows/linux-eic-shell.yml | 66 +++++++++++++-------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/.github/workflows/linux-eic-shell.yml b/.github/workflows/linux-eic-shell.yml index 61fffccb6d..708249385f 100644 --- a/.github/workflows/linux-eic-shell.yml +++ b/.github/workflows/linux-eic-shell.yml @@ -726,24 +726,18 @@ jobs: platform-release: "${{ env.platform }}:${{ env.release }}" setup: "/opt/detector/epic-${{ env.detector-version }}/bin/thisepic.sh" run: | - # Rename all jana dot files with matrix test name prefix for dotfile in jana*.dot; do - if [ -f "$dotfile" ]; then - newname="rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.${dotfile#jana.}" - # Handle the case where jana.dot should become rec_..._dot without extra period - if [ "$dotfile" = "jana.dot" ]; then - newname="rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.dot" - fi - mv "$dotfile" "$newname" + # Rename all jana dot files with matrix test name prefix + newname="rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.${dotfile#jana.}" + # Handle the case where jana.dot should become rec_..._dot without extra period + if [ "$dotfile" = "jana.dot" ]; then + newname="rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.dot" fi - done + mv "$dotfile" "$newname" - # Convert all dot files to SVG (both main and plugin-specific files) - for dotfile in *.dot; do - if [ -f "$dotfile" ]; then - svgfile="${dotfile%.dot}.svg" - sed '/rank=sink/s/"podio::Frame";//; /podio::Frame/d; /rank=source/s/"JEventProcessorPODIO";//; /JEventProcessorPODIO/d' "$dotfile" | dot -Tsvg > "$svgfile" - fi + # Convert all dot files to SVG (both main and plugin-specific files) + svgfile="${newname%.dot}.svg" + sed '/rank=sink/s/"podio::Frame";//; /podio::Frame/d; /rank=source/s/"JEventProcessorPODIO";//; /JEventProcessorPODIO/d' "$newname" | dot -Tsvg > "$svgfile" done continue-on-error: true - uses: actions/upload-artifact@v4 @@ -836,17 +830,18 @@ jobs: platform-release: "${{ env.platform }}:${{ env.release }}" setup: "/opt/detector/epic-${{ env.detector-version }}/bin/thisepic.sh" run: | - # Rename main file and convert all dot files to SVG - if [ -f jana.dot ]; then - mv jana.dot rec_${{ matrix.particle }}_EcalLumiSpec_${{ matrix.detector_config }}.dot - fi - - # Convert all dot files to SVG (both main and plugin-specific files) - for dotfile in *.dot; do - if [ -f "$dotfile" ]; then - svgfile="${dotfile%.dot}.svg" - sed '/rank=sink/s/"podio::Frame";//; /podio::Frame/d; /rank=source/s/"JEventProcessorPODIO";//; /JEventProcessorPODIO/d' "$dotfile" | dot -Tsvg > "$svgfile" + for dotfile in jana*.dot; do + # Rename all jana dot files with matrix test name prefix + newname="rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.${dotfile#jana.}" + # Handle the case where jana.dot should become rec_..._dot without extra period + if [ "$dotfile" = "jana.dot" ]; then + newname="rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.dot" fi + mv "$dotfile" "$newname" + + # Convert all dot files to SVG (both main and plugin-specific files) + svgfile="${newname%.dot}.svg" + sed '/rank=sink/s/"podio::Frame";//; /podio::Frame/d; /rank=source/s/"JEventProcessorPODIO";//; /JEventProcessorPODIO/d' "$newname" | dot -Tsvg > "$svgfile" done continue-on-error: true - uses: actions/upload-artifact@v4 @@ -953,17 +948,18 @@ jobs: platform-release: "${{ env.platform }}:${{ env.release }}" setup: "/opt/detector/epic-${{ env.detector-version }}/bin/thisepic.sh" run: | - # Rename main file and convert all dot files to SVG - if [ -f jana.dot ]; then - mv jana.dot rec_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}${{matrix.nthreads > 0 && '_MT' || ''}}.dot - fi - - # Convert all dot files to SVG (both main and plugin-specific files) - for dotfile in *.dot; do - if [ -f "$dotfile" ]; then - svgfile="${dotfile%.dot}.svg" - sed '/rank=sink/s/"podio::Frame";//; /podio::Frame/d; /rank=source/s/"JEventProcessorPODIO";//; /JEventProcessorPODIO/d' "$dotfile" | dot -Tsvg > "$svgfile" + for dotfile in jana*.dot; do + # Rename all jana dot files with matrix test name prefix + newname="rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.${dotfile#jana.}" + # Handle the case where jana.dot should become rec_..._dot without extra period + if [ "$dotfile" = "jana.dot" ]; then + newname="rec_${{ matrix.particle }}_1GeV_20GeV_${{ matrix.detector_config }}.dot" fi + mv "$dotfile" "$newname" + + # Convert all dot files to SVG (both main and plugin-specific files) + svgfile="${newname%.dot}.svg" + sed '/rank=sink/s/"podio::Frame";//; /podio::Frame/d; /rank=source/s/"JEventProcessorPODIO";//; /JEventProcessorPODIO/d' "$newname" | dot -Tsvg > "$svgfile" done continue-on-error: true - uses: actions/upload-artifact@v4 From a82d7b3d51bbb05f2cecd53d116cdf20a1c7b2a4 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 23 Aug 2025 17:41:03 -0500 Subject: [PATCH 23/29] Replace GetParameterNames with FilterParameters --- src/utilities/janadot/JEventProcessorJANADOT.cc | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index e60d987bf0..c9324e77b2 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -48,14 +48,10 @@ void JEventProcessorJANADOT::Init() { "janadot:split_criteria", split_criteria, "Criteria for splitting graphs: size, components, type, plugin, groups"); - // Also check for JANADOT:GROUP parameters (command line group definitions) - auto parameter_keys = params->GetParameterNames(); - for (const auto& key : parameter_keys) { - if (key.find("janadot:group:") == 0) { - std::string group_name = key.substr(13); // Remove "janadot:group:" prefix - std::string group_definition; - params->GetParameter(key, group_definition); - + // Check for janadot:group parameters (command line group definitions) + std::map parameter_keys; + params->FilterParameters(parameter_keys, "janadot:group:"); + for (const auto& [group_name, group_definition] : parameter_keys) { // Parse command line group definition similar to file format std::vector factories; std::string color = "lightblue"; // default color @@ -77,7 +73,6 @@ void JEventProcessorJANADOT::Init() { for (const auto& factory : factories) { nametag_to_group[factory] = group_name; } - } } } From 5af52d181c8e7be34efdedc5dd47eff7fbcd4d31 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Aug 2025 22:41:27 +0000 Subject: [PATCH 24/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../janadot/JEventProcessorJANADOT.cc | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index c9324e77b2..fb784fc66c 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -49,30 +49,30 @@ void JEventProcessorJANADOT::Init() { "Criteria for splitting graphs: size, components, type, plugin, groups"); // Check for janadot:group parameters (command line group definitions) - std::map parameter_keys; + std::map parameter_keys; params->FilterParameters(parameter_keys, "janadot:group:"); for (const auto& [group_name, group_definition] : parameter_keys) { - // Parse command line group definition similar to file format - std::vector factories; - std::string color = "lightblue"; // default color - - std::stringstream ss(group_definition); - std::string item; - while (std::getline(ss, item, ',')) { - if (item.find("color_") == 0) { - color = item.substr(6); // remove "color_" prefix - } else if (!item.empty()) { - factories.push_back(item); - } + // Parse command line group definition similar to file format + std::vector factories; + std::string color = "lightblue"; // default color + + std::stringstream ss(group_definition); + std::string item; + while (std::getline(ss, item, ',')) { + if (item.find("color_") == 0) { + color = item.substr(6); // remove "color_" prefix + } else if (!item.empty()) { + factories.push_back(item); } + } - user_groups[group_name] = factories; - user_group_colors[group_name] = color; + user_groups[group_name] = factories; + user_group_colors[group_name] = color; - // Build nametag to group mapping - for (const auto& factory : factories) { - nametag_to_group[factory] = group_name; - } + // Build nametag to group mapping + for (const auto& factory : factories) { + nametag_to_group[factory] = group_name; + } } } From b3711e1ba910fd7925ed9ce84d144249d233185d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 22:51:43 +0000 Subject: [PATCH 25/29] Remove size, components, type split_criteria and fix compilation error Co-authored-by: wdconinc <4656391+wdconinc@users.noreply.github.com> --- .../janadot/JEventProcessorJANADOT.cc | 180 +----------------- .../janadot/JEventProcessorJANADOT.h | 5 - 2 files changed, 5 insertions(+), 180 deletions(-) diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index fb784fc66c..de8b9ea99f 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -35,18 +35,10 @@ void JEventProcessorJANADOT::Init() { params->SetDefaultParameter("janadot:enable_splitting", enable_splitting, "Enable splitting large graphs into multiple files"); - max_nodes_per_graph = 50; - params->SetDefaultParameter("janadot:max_nodes_per_graph", max_nodes_per_graph, - "Maximum number of nodes per graph when splitting"); - - max_edges_per_graph = 100; - params->SetDefaultParameter("janadot:max_edges_per_graph", max_edges_per_graph, - "Maximum number of edges per graph when splitting"); - split_criteria = "plugin"; params->SetDefaultParameter( "janadot:split_criteria", split_criteria, - "Criteria for splitting graphs: size, components, type, plugin, groups"); + "Criteria for splitting graphs: plugin, groups"); // Check for janadot:group parameters (command line group definitions) std::map parameter_keys; @@ -266,16 +258,10 @@ void JEventProcessorJANADOT::WriteSplitDotFiles() { std::map> plugin_groups; // Use switch/case for better structure - enum SplitMethod { PLUGIN, SIZE, COMPONENTS, TYPE, GROUPS } method = PLUGIN; + enum SplitMethod { PLUGIN, GROUPS } method = PLUGIN; if (split_criteria == "plugin") { method = PLUGIN; - } else if (split_criteria == "size") { - method = SIZE; - } else if (split_criteria == "components") { - method = COMPONENTS; - } else if (split_criteria == "type") { - method = TYPE; } else if (split_criteria == "groups") { method = GROUPS; } else { @@ -297,43 +283,7 @@ void JEventProcessorJANADOT::WriteSplitDotFiles() { WriteGroupGraphs(plugin_groups); WriteOverallDotFile(plugin_groups); return; - - case SIZE: - node_groups = SplitGraphBySize(); - break; - - case COMPONENTS: - node_groups = SplitGraphByConnectedComponents(); - break; - - case TYPE: - node_groups = SplitGraphByType(); - break; - } - - // For size, components, and type splitting - std::cout << "Splitting graph into " << node_groups.size() << " subgraphs" << std::endl; - - for (size_t i = 0; i < node_groups.size(); i++) { - // Create filename for this subgraph - std::string base_filename = output_filename; - size_t dot_pos = base_filename.find_last_of('.'); - if (dot_pos != std::string::npos) { - base_filename = base_filename.substr(0, dot_pos); - } - - std::stringstream ss; - ss << base_filename << "_part" << std::setfill('0') << std::setw(3) << (i + 1) << ".dot"; - std::string filename = ss.str(); - - WriteSplitDotFile(filename, node_groups[i]); } - - // Write an index file explaining the split - WriteIndexFile(node_groups.size()); - - // Write overall summary for all split types - WriteOverallDotFile(node_groups); } void JEventProcessorJANADOT::WriteSplitDotFile(const std::string& filename, @@ -460,8 +410,6 @@ void JEventProcessorJANADOT::WriteIndexFile(int num_parts) { ofs << std::endl; ofs << "Configuration used:" << std::endl; ofs << " Split criteria: " << split_criteria << std::endl; - ofs << " Max nodes per graph: " << max_nodes_per_graph << std::endl; - ofs << " Max edges per graph: " << max_edges_per_graph << std::endl; ofs.close(); @@ -547,126 +495,8 @@ void JEventProcessorJANADOT::AnalyzeGraph(int& total_nodes, int& total_edges) { } bool JEventProcessorJANADOT::ShouldSplitGraph(int total_nodes, int total_edges) { - return (total_nodes > max_nodes_per_graph) || (total_edges > max_edges_per_graph); -} - -std::vector> JEventProcessorJANADOT::SplitGraphBySize() { - std::vector> groups; - std::set current_group; - - int current_nodes = 0; - int current_edges = 0; - - // Simple greedy algorithm: add nodes to current group until limits are reached - for (auto& [nametag, fstats] : factory_stats) { - // Count edges involving this node - int node_edges = 0; - for (auto& [link, stats] : call_links) { - std::string caller = MakeNametag(link.caller_name, link.caller_tag); - std::string callee = MakeNametag(link.callee_name, link.callee_tag); - if (caller == nametag || callee == nametag) { - // Only count edge if both nodes are in current group (or this is the first node) - if (current_group.empty() || current_group.find(caller) != current_group.end() || - current_group.find(callee) != current_group.end()) { - node_edges++; - } - } - } - - // Check if adding this node would exceed limits - if (current_nodes > 0 && (current_nodes + 1 > max_nodes_per_graph || - current_edges + node_edges > max_edges_per_graph)) { - groups.push_back(current_group); - current_group.clear(); - current_nodes = 0; - current_edges = 0; - } - - current_group.insert(nametag); - current_nodes++; - current_edges += node_edges; - } - - if (!current_group.empty()) { - groups.push_back(current_group); - } - - // If we only have one group, return it as-is (even if it's large) - if (groups.empty()) { - std::set all_nodes; - for (auto& [nametag, fstats] : factory_stats) { - all_nodes.insert(nametag); - } - groups.push_back(all_nodes); - } - - return groups; -} - -std::vector> JEventProcessorJANADOT::SplitGraphByConnectedComponents() { - // Implementation of connected components finding using Union-Find - std::map parent; - - // Initialize each node as its own parent - for (auto& [nametag, fstats] : factory_stats) { - parent[nametag] = nametag; - } - - // Union-Find helper functions - std::function find = [&](const std::string& x) -> std::string { - if (parent[x] != x) { - parent[x] = find(parent[x]); - } - return parent[x]; - }; - - auto unite = [&](const std::string& x, const std::string& y) { - std::string px = find(x); - std::string py = find(y); - if (px != py) { - parent[px] = py; - } - }; - - // Connect nodes that have edges between them - for (auto& [link, stats] : call_links) { - std::string caller = MakeNametag(link.caller_name, link.caller_tag); - std::string callee = MakeNametag(link.callee_name, link.callee_tag); - unite(caller, callee); - } - - // Group nodes by their root parent - std::map> components; - for (auto& [nametag, fstats] : factory_stats) { - components[find(nametag)].insert(nametag); - } - - // Convert to vector of sets - std::vector> groups; - for (auto& [root, component] : components) { - groups.push_back(component); - } - - return groups; -} - -std::vector> JEventProcessorJANADOT::SplitGraphByType() { - std::map> type_groups; - - // Group nodes by their type - for (auto& [nametag, fstats] : factory_stats) { - type_groups[fstats.type].insert(nametag); - } - - // Convert to vector of sets - std::vector> groups; - for (auto& [type, group] : type_groups) { - if (!group.empty()) { - groups.push_back(group); - } - } - - return groups; + // For plugin and groups splitting, we always split if there are multiple groups + return true; } void JEventProcessorJANADOT::WritePluginGraphs( @@ -1140,7 +970,7 @@ void JEventProcessorJANADOT::WriteGroupDotFile(const std::string& group_name, ofs << ",shape=" << GetNodeShape(fstats.type); // Add timing information to label - double total_time = fstats.total_time_waiting_on + fstats.total_time_factory_active; + double total_time = fstats.time_waited_on + fstats.time_waiting; std::string time_str = MakeTimeString(total_time); ofs << ",label=\"" << nametag << "\\n" << time_str << "\""; ofs << "];" << std::endl; diff --git a/src/utilities/janadot/JEventProcessorJANADOT.h b/src/utilities/janadot/JEventProcessorJANADOT.h index 77db63e164..d702bf748b 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.h +++ b/src/utilities/janadot/JEventProcessorJANADOT.h @@ -92,8 +92,6 @@ class JEventProcessorJANADOT : public JEventProcessor { // Configuration parameters std::string output_filename; bool enable_splitting; - int max_nodes_per_graph; - int max_edges_per_graph; std::string split_criteria; // Group-related parameters @@ -118,9 +116,6 @@ class JEventProcessorJANADOT : public JEventProcessor { void WriteOverallDotFile(const std::map>& plugin_groups); void WriteOverallDotFile(const std::vector>& node_groups); void WriteIndexFile(int num_parts); - std::vector> SplitGraphByConnectedComponents(); - std::vector> SplitGraphBySize(); - std::vector> SplitGraphByType(); std::map> SplitGraphByPlugin(); std::map> SplitGraphByGroups(); void WriteGroupGraphs(const std::map>& groups); From 2da8f63874adad0d871bf99b1ddfe02f33e92612 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Aug 2025 23:19:41 +0000 Subject: [PATCH 26/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/utilities/janadot/JEventProcessorJANADOT.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index de8b9ea99f..e0c45b3429 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -36,9 +36,8 @@ void JEventProcessorJANADOT::Init() { "Enable splitting large graphs into multiple files"); split_criteria = "plugin"; - params->SetDefaultParameter( - "janadot:split_criteria", split_criteria, - "Criteria for splitting graphs: plugin, groups"); + params->SetDefaultParameter("janadot:split_criteria", split_criteria, + "Criteria for splitting graphs: plugin, groups"); // Check for janadot:group parameters (command line group definitions) std::map parameter_keys; From 1ac1e54628a82c3da98bf64fefaccfa159693800 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 23 Aug 2025 18:31:15 -0500 Subject: [PATCH 27/29] Remove unused variables and methods --- .../janadot/JEventProcessorJANADOT.cc | 18 +----------------- src/utilities/janadot/JEventProcessorJANADOT.h | 4 ---- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index e0c45b3429..491ccc2244 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -149,13 +149,7 @@ void JEventProcessorJANADOT::Process(const std::shared_ptr& event) void JEventProcessorJANADOT::Finish() { WriteDotFile(); } void JEventProcessorJANADOT::WriteDotFile() { - int total_nodes, total_edges; - AnalyzeGraph(total_nodes, total_edges); - - std::cout << "Graph analysis: " << total_nodes << " nodes, " << total_edges << " edges" - << std::endl; - - if (enable_splitting && ShouldSplitGraph(total_nodes, total_edges)) { + if (enable_splitting) { std::cout << "Graph is large, splitting into multiple files..." << std::endl; WriteSplitDotFiles(); } else { @@ -488,16 +482,6 @@ std::string JEventProcessorJANADOT::GetNodeShape(node_type type) { } } -void JEventProcessorJANADOT::AnalyzeGraph(int& total_nodes, int& total_edges) { - total_nodes = factory_stats.size(); - total_edges = call_links.size(); -} - -bool JEventProcessorJANADOT::ShouldSplitGraph(int total_nodes, int total_edges) { - // For plugin and groups splitting, we always split if there are multiple groups - return true; -} - void JEventProcessorJANADOT::WritePluginGraphs( const std::map>& plugin_groups) { std::cout << "Splitting graph into " << plugin_groups.size() << " plugin-based subgraphs" diff --git a/src/utilities/janadot/JEventProcessorJANADOT.h b/src/utilities/janadot/JEventProcessorJANADOT.h index d702bf748b..f8c70a20e5 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.h +++ b/src/utilities/janadot/JEventProcessorJANADOT.h @@ -121,8 +121,4 @@ class JEventProcessorJANADOT : public JEventProcessor { void WriteGroupGraphs(const std::map>& groups); void WriteGroupDotFile(const std::string& group_name, const std::set& nodes); std::string ExtractPluginName(const std::string& nametag); - - // Graph analysis methods - void AnalyzeGraph(int& total_nodes, int& total_edges); - bool ShouldSplitGraph(int total_nodes, int total_edges); }; From dd4597772dc430d7653f268d20c366fd9b9e21c5 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 23 Aug 2025 18:34:19 -0500 Subject: [PATCH 28/29] Remove debugging output --- src/utilities/janadot/JEventProcessorJANADOT.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index 491ccc2244..acd58e46f7 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -81,7 +81,6 @@ void JEventProcessorJANADOT::Process(const std::shared_ptr& event) for (auto* factory : factories) { std::string nametag = MakeNametag(factory->GetObjectName(), factory->GetTag()); std::string plugin_name = factory->GetPluginName(); - std::cout << nametag << " " << plugin_name << std::endl; // If plugin name is empty, try to use a reasonable default if (plugin_name.empty()) { From 2979a0cccf95881b0938962d0564ac23c89bfb9b Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 23 Aug 2025 19:31:25 -0500 Subject: [PATCH 29/29] Add janadot plugin with plugin-based and group-based graph splitting for enhanced call graph visualization (fix: iwyu) (#2047) This PR applies the include-what-you-use fixes as suggested by https://github.com/eic/EICrecon/actions/runs/17181417657. Please merge this PR into the branch `copilot/fix-fa2a3ce4-8a21-441e-9641-77391e5b455f` to resolve failures in PR #2040. Auto-generated by [create-pull-request][1] [1]: https://github.com/peter-evans/create-pull-request Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/utilities/janadot/JEventProcessorJANADOT.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utilities/janadot/JEventProcessorJANADOT.cc b/src/utilities/janadot/JEventProcessorJANADOT.cc index acd58e46f7..f9e133d2b6 100644 --- a/src/utilities/janadot/JEventProcessorJANADOT.cc +++ b/src/utilities/janadot/JEventProcessorJANADOT.cc @@ -15,10 +15,8 @@ #include #include #include -#include #include #include -#include #include #include #include