-
Notifications
You must be signed in to change notification settings - Fork 34
[Solvergraph] Add callback API for node tracking #1890
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -269,9 +269,155 @@ class PatchDataLayerToVtk : public shamrock::solvergraph::INode { | |
| std::string _impl_get_tex() { return "TODO"; } | ||
| }; | ||
|
|
||
| class GraphTracer { | ||
| public: | ||
| std::string filename; | ||
|
|
||
| std::queue<std::string> events; | ||
|
|
||
| public: | ||
| inline void add_event(std::string event) { events.push(event); } | ||
|
|
||
| inline void maybe_flush() { | ||
|
|
||
| // open the file for writing | ||
| std::ofstream file(filename, std::ios::app); | ||
|
|
||
| // lock the file with flock | ||
|
|
||
| // flush the events | ||
| while (!events.empty()) { | ||
| std::string event = events.front(); | ||
| events.pop(); | ||
| file << event << std::endl; | ||
| } | ||
|
|
||
| // close the file | ||
| file.close(); | ||
| } | ||
| }; | ||
|
|
||
| GraphTracer graph_tracer("graph_trace.txt"); | ||
|
Comment on lines
+272
to
+300
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue: Missing Constructor, MPI Race Condition, and Static Initialization Order Fiasco
Solution
class GraphTracer {
public:
std::string filename;
std::queue<std::string> events;
explicit GraphTracer(std::string filename) : filename(std::move(filename)) {}
~GraphTracer() {
maybe_flush();
}
inline void add_event(std::string event) { events.push(event); }
inline void maybe_flush() {
std::ofstream file(filename, std::ios::app);
if (!file) {
return;
}
while (!events.empty()) {
std::string event = events.front();
events.pop();
file << event << std::endl;
}
file.close();
}
};
inline GraphTracer& get_graph_tracer() {
static GraphTracer tracer("graph_trace_" + std::to_string(shamcomm::world_rank()) + ".txt");
return tracer;
} |
||
|
|
||
| void on_node_create(u64 uuid) { | ||
| graph_tracer.add_event( | ||
| nlohmann::json{ | ||
| {"event", "node_create"}, | ||
| {"uuid", uuid}, | ||
| {"wtime", shambase::details::get_wtime()}, | ||
| } | ||
| .dump()); | ||
| graph_tracer.maybe_flush(); | ||
| } | ||
|
|
||
| void on_node_destroy(u64 uuid) { | ||
| graph_tracer.add_event( | ||
| nlohmann::json{ | ||
| {"event", "node_destroy"}, | ||
| {"uuid", uuid}, | ||
| {"wtime", shambase::details::get_wtime()}, | ||
| } | ||
| .dump()); | ||
| graph_tracer.maybe_flush(); | ||
| } | ||
|
|
||
| void on_edge_create(u64 uuid) { | ||
| graph_tracer.add_event( | ||
| nlohmann::json{ | ||
| {"event", "edge_create"}, | ||
| {"uuid", uuid}, | ||
| {"wtime", shambase::details::get_wtime()}, | ||
| } | ||
| .dump()); | ||
| } | ||
|
|
||
| void on_edge_destroy(u64 uuid) { | ||
| graph_tracer.add_event( | ||
| nlohmann::json{ | ||
| {"event", "edge_destroy"}, | ||
| {"uuid", uuid}, | ||
| {"wtime", shambase::details::get_wtime()}, | ||
| } | ||
| .dump()); | ||
| } | ||
|
Comment on lines
+324
to
+342
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The callbacks auto log_event = [&](const std::string& event, u64 uuid) {
graph_tracer.add_event(
nlohmann::json{
{"event", event},
{"uuid", uuid},
{"wtime", shambase::details::get_wtime()},
}
.dump());
graph_tracer.maybe_flush();
};
void on_edge_create(u64 uuid) {
log_event("edge_create", uuid);
}
void on_edge_destroy(u64 uuid) {
log_event("edge_destroy", uuid);
}References
|
||
|
|
||
| void on_node_update(shamrock::solvergraph::INode &node) { | ||
|
|
||
| nlohmann::json edge_list = nlohmann::json::array(); | ||
|
|
||
| node.on_edge_ro_edges([&](shamrock::solvergraph::IEdge &e) { | ||
| edge_list.push_back( | ||
| nlohmann::json{{"type", "read"}, {"label", e.get_label()}, {"uuid", e.get_uuid()}}); | ||
| }); | ||
|
|
||
| node.on_edge_rw_edges([&](shamrock::solvergraph::IEdge &e) { | ||
| edge_list.push_back( | ||
| nlohmann::json{ | ||
| {"type", "read_write"}, {"label", e.get_label()}, {"uuid", e.get_uuid()}}); | ||
| }); | ||
|
|
||
| graph_tracer.add_event( | ||
| nlohmann::json{ | ||
| {"event", "node_update"}, | ||
| {"uuid", node.get_uuid()}, | ||
| {"type", typeid(node).name()}, | ||
| {"label", node.get_label()}, | ||
| {"wtime", shambase::details::get_wtime()}, | ||
| {"edges", edge_list}} | ||
| .dump()); | ||
| graph_tracer.maybe_flush(); | ||
| } | ||
|
|
||
| void on_node_op(u64 uuid, u64 op_id) { | ||
| if (op_id == 0) { | ||
| graph_tracer.add_event( | ||
| nlohmann::json{ | ||
| {"event", "node_evaluate_begin"}, | ||
| {"uuid", uuid}, | ||
| {"wtime", shambase::details::get_wtime()}, | ||
| } | ||
| .dump()); | ||
| } else if (op_id == 1) { | ||
| graph_tracer.add_event( | ||
| nlohmann::json{ | ||
| {"event", "node_evaluate_end"}, | ||
| {"uuid", uuid}, | ||
| {"wtime", shambase::details::get_wtime()}, | ||
| } | ||
| .dump()); | ||
| } else { | ||
| shambase::throw_with_loc<std::runtime_error>(shambase::format("Invalid op_id: {}", op_id)); | ||
| } | ||
| graph_tracer.maybe_flush(); | ||
| } | ||
|
|
||
| void on_node_evaluate_end(u64 uuid) { | ||
| graph_tracer.add_event( | ||
| nlohmann::json{ | ||
| {"event", "node_evaluate_end"}, | ||
| {"uuid", uuid}, | ||
| {"wtime", shambase::details::get_wtime()}, | ||
| } | ||
| .dump()); | ||
| graph_tracer.maybe_flush(); | ||
| } | ||
|
Comment on lines
+394
to
+403
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Comment on lines
+394
to
+403
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| template<class Tvec, class TgridVec> | ||
| void shammodels::basegodunov::Solver<Tvec, TgridVec>::init_solver_graph() { | ||
|
|
||
|
Comment on lines
406
to
407
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a parallel MPI execution, multiple ranks will run concurrently. If all ranks write to the same hardcoded file "graph_trace.txt" without synchronization, the file writes will interleave, leading to corrupted and unparseable JSON data. To prevent this, the filename should be rank-specific, for example by appending template<class Tvec, class TgridVec>
void shammodels::basegodunov::Solver<Tvec, TgridVec>::init_solver_graph() {
graph_tracer.filename = "graph_trace_" + std::to_string(shamcomm::world_rank()) + ".txt"; |
||
| shamrock::solvergraph::LifetimeTracker<shamrock::solvergraph::INode>::on_create | ||
| = on_node_create; | ||
| shamrock::solvergraph::LifetimeTracker<shamrock::solvergraph::INode>::on_destroy | ||
| = on_node_destroy; | ||
| shamrock::solvergraph::LifetimeTracker<shamrock::solvergraph::INode>::on_state_update | ||
| = on_node_update; | ||
| shamrock::solvergraph::LifetimeTracker<shamrock::solvergraph::INode>::on_op = on_node_op; | ||
|
|
||
| shamrock::solvergraph::LifetimeTracker<shamrock::solvergraph::IEdge>::on_create | ||
| = on_edge_create; | ||
| shamrock::solvergraph::LifetimeTracker<shamrock::solvergraph::IEdge>::on_destroy | ||
| = on_edge_destroy; | ||
|
|
||
| bool enable_mem_free = false; | ||
|
|
||
| auto get_optional_free_mem = [&](auto &bind_to, auto &add_to) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,21 +18,33 @@ | |
|
|
||
| #include "shambase/WithUUID.hpp" | ||
| #include "shambase/aliases_int.hpp" | ||
| #include "shambase/memory.hpp" | ||
| #include "shamrock/solvergraph/IFreeable.hpp" | ||
| #include "shamrock/solvergraph/LifetimeTracker.hpp" | ||
| #include <string> | ||
|
|
||
| namespace shamrock::solvergraph { | ||
|
|
||
| class INode; | ||
|
|
||
| class IEdge : public shambase::WithUUID<IEdge, u64>, public IFreeable { | ||
| class IEdge : public IFreeable { | ||
|
|
||
| /// track the UUID of the node to notify changes. | ||
| /// Sadly we can not use a WithUUID directly since it is hard to log destruction in the | ||
| /// destructor if the object was moved which could lead to double free notifications. | ||
| std::shared_ptr<LifetimeTracker<IEdge>> tracker | ||
| = std::make_shared<LifetimeTracker<IEdge>>(); | ||
|
|
||
| public: | ||
| inline std::string get_label() const { return _impl_get_dot_label(); } | ||
| inline std::string get_tex_symbol() const { return _impl_get_tex_symbol(); } | ||
|
|
||
| virtual std::string _impl_get_dot_label() const = 0; | ||
| virtual std::string _impl_get_tex_symbol() const = 0; | ||
|
|
||
| /// Get the UUID of the node | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| inline u64 get_uuid() const { return shambase::get_check_ref(tracker).get_uuid(); } | ||
|
|
||
| inline virtual ~IEdge() {} | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,63 @@ | ||||||||||||||||||
| // -------------------------------------------------------// | ||||||||||||||||||
| // | ||||||||||||||||||
| // SHAMROCK code for hydrodynamics | ||||||||||||||||||
| // Copyright (c) 2021-2026 Timothée David--Cléris <tim.shamrock@proton.me> | ||||||||||||||||||
| // SPDX-License-Identifier: CeCILL Free Software License Agreement v2.1 | ||||||||||||||||||
| // Shamrock is licensed under the CeCILL 2.1 License, see LICENSE for more information | ||||||||||||||||||
| // | ||||||||||||||||||
| // -------------------------------------------------------// | ||||||||||||||||||
|
|
||||||||||||||||||
| #pragma once | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * @file LifetimeTracker.hpp | ||||||||||||||||||
| * @author Timothée David--Cléris (tim.shamrock@proton.me) | ||||||||||||||||||
| * @brief | ||||||||||||||||||
| * | ||||||||||||||||||
| */ | ||||||||||||||||||
|
|
||||||||||||||||||
| #include "shambase/WithUUID.hpp" | ||||||||||||||||||
| #include "shambase/aliases_int.hpp" | ||||||||||||||||||
| namespace shamrock::solvergraph { | ||||||||||||||||||
|
|
||||||||||||||||||
| template<typename T> | ||||||||||||||||||
| class LifetimeTracker : public shambase::WithUUID<LifetimeTracker<T>, u64> { | ||||||||||||||||||
| public: | ||||||||||||||||||
| inline static void (*on_create)(u64 uuid) = nullptr; | ||||||||||||||||||
| inline static void (*on_destroy)(u64 uuid) = nullptr; | ||||||||||||||||||
|
|
||||||||||||||||||
| inline static void (*on_state_update)(T &node) = nullptr; | ||||||||||||||||||
| inline static void (*on_op)(u64 uuid, u64 op_id) = nullptr; | ||||||||||||||||||
|
|
||||||||||||||||||
| LifetimeTracker() : shambase::WithUUID<LifetimeTracker, u64>() { | ||||||||||||||||||
| if (on_create != nullptr) { | ||||||||||||||||||
| on_create(this->get_uuid()); | ||||||||||||||||||
| } | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| LifetimeTracker(const LifetimeTracker &) = delete; | ||||||||||||||||||
| LifetimeTracker &operator=(const LifetimeTracker &) = delete; | ||||||||||||||||||
|
|
||||||||||||||||||
| LifetimeTracker(LifetimeTracker &&) noexcept = default; | ||||||||||||||||||
| LifetimeTracker &operator=(LifetimeTracker &&) noexcept = default; | ||||||||||||||||||
|
Comment on lines
+41
to
+42
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential Double-Destroy Bug on MoveIf a Since
Suggested change
Comment on lines
+41
to
+42
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| inline void notify_update(T &node) { | ||||||||||||||||||
| if (on_state_update != nullptr) { | ||||||||||||||||||
| on_state_update(node); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| inline void notify_op(u64 op_id) { | ||||||||||||||||||
| if (on_op != nullptr) { | ||||||||||||||||||
| on_op(this->get_uuid(), op_id); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| ~LifetimeTracker() { | ||||||||||||||||||
| if (on_destroy != nullptr) { | ||||||||||||||||||
| on_destroy(this->get_uuid()); | ||||||||||||||||||
| } | ||||||||||||||||||
| }; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| } // namespace shamrock::solvergraph | ||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opening and closing the file stream on every single event flush (
std::ofstream file(filename, std::ios::app)) introduces a significant performance bottleneck, especially in high-performance simulation codes. Consider keeping the file stream open as a member ofGraphTracer(opening it once during initialization and closing it in the destructor), and callingfile.flush()or letting the buffer flush naturally to minimize disk I/O overhead.