From 3ac17ab0d83dd786cd4d541f2428e7e07d0d10eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20David--Cl=C3=A9ris?= Date: Tue, 23 Jun 2026 15:39:59 +0200 Subject: [PATCH 1/2] basic node tracking --- src/shammodels/ramses/src/Solver.cpp | 22 +++++++ .../include/shamrock/solvergraph/INode.hpp | 59 +++++++++++++++++-- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/shammodels/ramses/src/Solver.cpp b/src/shammodels/ramses/src/Solver.cpp index 9c43b8f885..cc0c978c8d 100644 --- a/src/shammodels/ramses/src/Solver.cpp +++ b/src/shammodels/ramses/src/Solver.cpp @@ -269,9 +269,31 @@ class PatchDataLayerToVtk : public shamrock::solvergraph::INode { std::string _impl_get_tex() { return "TODO"; } }; +void on_node_create(u64 uuid) { + shamcomm::logs::info_ln( + "Solver", + "Node created with UUID =", + uuid); +} + +void on_node_destroy(u64 uuid) { + shamcomm::logs::info_ln("Solver", "Node destroyed with UUID =", uuid); +} + +void on_node_update(shamrock::solvergraph::INode &node) { + shamcomm::logs::info_ln("Solver", "Node updated with UUID =", node.get_uuid(), "type =", typeid(node).name(), "label =", node.get_label()); +} + template void shammodels::basegodunov::Solver::init_solver_graph() { + shamrock::solvergraph::LifetimeTracker::on_create + = on_node_create; + shamrock::solvergraph::LifetimeTracker::on_destroy + = on_node_destroy; + shamrock::solvergraph::LifetimeTracker::on_update + = on_node_update; + bool enable_mem_free = false; auto get_optional_free_mem = [&](auto &bind_to, auto &add_to) { diff --git a/src/shamrock/include/shamrock/solvergraph/INode.hpp b/src/shamrock/include/shamrock/solvergraph/INode.hpp index 6ca3f0606d..f93609d10a 100644 --- a/src/shamrock/include/shamrock/solvergraph/INode.hpp +++ b/src/shamrock/include/shamrock/solvergraph/INode.hpp @@ -25,14 +25,52 @@ namespace shamrock::solvergraph { + template + class LifetimeTracker : public shambase::WithUUID, u64> { + public: + inline static void (*on_create)(u64 uuid) = nullptr; + inline static void (*on_destroy)(u64 uuid) = nullptr; + + inline static void (*on_update)(T &node) = nullptr; + + LifetimeTracker() : shambase::WithUUID() { + 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; + + inline void notify_update(T &node) { + if (on_update != nullptr) { + on_update(node); + } + } + + ~LifetimeTracker() { + if (on_destroy != nullptr) { + on_destroy(this->get_uuid()); + } + }; + }; + /// Inode is node between data edges, takes multiple inputs, multiple outputs - class INode : public std::enable_shared_from_this, - public shambase::WithUUID { + class INode : public std::enable_shared_from_this { /// Read only edges - std::vector> ro_edges; + std::vector> ro_edges = {}; /// Read write edges - std::vector> rw_edges; + std::vector> rw_edges = {}; + + /// 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> tracker + = std::make_shared>(); public: INode() = default; @@ -46,6 +84,9 @@ namespace shamrock::solvergraph { /// Move assignment - automatically delegates to base classes and members INode &operator=(INode &&) noexcept = default; + /// Get the UUID of the node + inline u64 get_uuid() const { return shambase::get_check_ref(tracker).get_uuid(); } + /// Get a shared pointer to this node inline std::shared_ptr getptr_shared() { return shared_from_this(); } /// Get a weak pointer to this node @@ -71,6 +112,7 @@ namespace shamrock::solvergraph { /// Destructor (virtual) & reset the edges virtual ~INode() { + tracker.reset(); __internal_set_ro_edges({}); __internal_set_rw_edges({}); } @@ -124,6 +166,9 @@ namespace shamrock::solvergraph { /// Get the TeX of the node partial inline std::string get_tex_partial() { return _impl_get_tex(); }; + /// Get the label of the node + inline std::string get_label() { return _impl_get_label(); }; + /// print the node info inline virtual std::string print_node_info() const { std::string node_info = shambase::format("Node info :\n"); @@ -176,6 +221,9 @@ namespace shamrock::solvergraph { for (auto e : ro_edges) { // shambase::get_check_ref(e).parent = getptr_weak(); } + if (tracker) { + tracker->notify_update(*this); + } } inline void INode::__internal_set_rw_edges(std::vector> new_rw_edges) { @@ -186,6 +234,9 @@ namespace shamrock::solvergraph { for (auto e : rw_edges) { // shambase::get_check_ref(e).child = getptr_weak(); } + if (tracker) { + tracker->notify_update(*this); + } } template From cc8e4954f1f5988b78ee6f27f6816a99decc5451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20David--Cl=C3=A9ris?= Date: Wed, 24 Jun 2026 00:08:23 +0200 Subject: [PATCH 2/2] basic node tracking --- src/shammodels/ramses/src/Solver.cpp | 138 +++++++++++++++++- .../include/shamrock/solvergraph/IEdge.hpp | 14 +- .../include/shamrock/solvergraph/INode.hpp | 44 ++---- .../shamrock/solvergraph/LifetimeTracker.hpp | 63 ++++++++ 4 files changed, 217 insertions(+), 42 deletions(-) create mode 100644 src/shamrock/include/shamrock/solvergraph/LifetimeTracker.hpp diff --git a/src/shammodels/ramses/src/Solver.cpp b/src/shammodels/ramses/src/Solver.cpp index cc0c978c8d..cee99ba3cc 100644 --- a/src/shammodels/ramses/src/Solver.cpp +++ b/src/shammodels/ramses/src/Solver.cpp @@ -269,19 +269,137 @@ class PatchDataLayerToVtk : public shamrock::solvergraph::INode { std::string _impl_get_tex() { return "TODO"; } }; +class GraphTracer { + public: + std::string filename; + + std::queue 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"); + void on_node_create(u64 uuid) { - shamcomm::logs::info_ln( - "Solver", - "Node created with UUID =", - 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) { - shamcomm::logs::info_ln("Solver", "Node destroyed with UUID =", 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()); } void on_node_update(shamrock::solvergraph::INode &node) { - shamcomm::logs::info_ln("Solver", "Node updated with UUID =", node.get_uuid(), "type =", typeid(node).name(), "label =", node.get_label()); + + 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(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(); } template @@ -291,8 +409,14 @@ void shammodels::basegodunov::Solver::init_solver_graph() { = on_node_create; shamrock::solvergraph::LifetimeTracker::on_destroy = on_node_destroy; - shamrock::solvergraph::LifetimeTracker::on_update + shamrock::solvergraph::LifetimeTracker::on_state_update = on_node_update; + shamrock::solvergraph::LifetimeTracker::on_op = on_node_op; + + shamrock::solvergraph::LifetimeTracker::on_create + = on_edge_create; + shamrock::solvergraph::LifetimeTracker::on_destroy + = on_edge_destroy; bool enable_mem_free = false; diff --git a/src/shamrock/include/shamrock/solvergraph/IEdge.hpp b/src/shamrock/include/shamrock/solvergraph/IEdge.hpp index 4ace79c490..efddfbfe95 100644 --- a/src/shamrock/include/shamrock/solvergraph/IEdge.hpp +++ b/src/shamrock/include/shamrock/solvergraph/IEdge.hpp @@ -18,14 +18,23 @@ #include "shambase/WithUUID.hpp" #include "shambase/aliases_int.hpp" +#include "shambase/memory.hpp" #include "shamrock/solvergraph/IFreeable.hpp" +#include "shamrock/solvergraph/LifetimeTracker.hpp" #include namespace shamrock::solvergraph { class INode; - class IEdge : public shambase::WithUUID, 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> tracker + = std::make_shared>(); + public: inline std::string get_label() const { return _impl_get_dot_label(); } inline std::string get_tex_symbol() const { return _impl_get_tex_symbol(); } @@ -33,6 +42,9 @@ namespace shamrock::solvergraph { virtual std::string _impl_get_dot_label() const = 0; virtual std::string _impl_get_tex_symbol() const = 0; + /// Get the UUID of the node + inline u64 get_uuid() const { return shambase::get_check_ref(tracker).get_uuid(); } + inline virtual ~IEdge() {} }; diff --git a/src/shamrock/include/shamrock/solvergraph/INode.hpp b/src/shamrock/include/shamrock/solvergraph/INode.hpp index f93609d10a..df7db7ed72 100644 --- a/src/shamrock/include/shamrock/solvergraph/INode.hpp +++ b/src/shamrock/include/shamrock/solvergraph/INode.hpp @@ -20,44 +20,12 @@ #include "shambase/memory.hpp" #include "shambase/stacktrace.hpp" #include "shamrock/solvergraph/IEdge.hpp" +#include "shamrock/solvergraph/LifetimeTracker.hpp" #include #include namespace shamrock::solvergraph { - template - class LifetimeTracker : public shambase::WithUUID, u64> { - public: - inline static void (*on_create)(u64 uuid) = nullptr; - inline static void (*on_destroy)(u64 uuid) = nullptr; - - inline static void (*on_update)(T &node) = nullptr; - - LifetimeTracker() : shambase::WithUUID() { - 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; - - inline void notify_update(T &node) { - if (on_update != nullptr) { - on_update(node); - } - } - - ~LifetimeTracker() { - if (on_destroy != nullptr) { - on_destroy(this->get_uuid()); - } - }; - }; - /// Inode is node between data edges, takes multiple inputs, multiple outputs class INode : public std::enable_shared_from_this { @@ -148,7 +116,15 @@ namespace shamrock::solvergraph { } /// Evaluate the node - inline void evaluate() { _impl_evaluate_internal(); } + inline void evaluate() { + if (tracker) { + tracker->notify_op(0); + } + _impl_evaluate_internal(); + if (tracker) { + tracker->notify_op(1); + } + } /// Get the dot graph of the node (Currently only an alias to get_dot_graph_partial) inline std::string get_dot_graph() { return get_dot_graph_partial(); }; diff --git a/src/shamrock/include/shamrock/solvergraph/LifetimeTracker.hpp b/src/shamrock/include/shamrock/solvergraph/LifetimeTracker.hpp new file mode 100644 index 0000000000..f4ecd2fd9e --- /dev/null +++ b/src/shamrock/include/shamrock/solvergraph/LifetimeTracker.hpp @@ -0,0 +1,63 @@ +// -------------------------------------------------------// +// +// SHAMROCK code for hydrodynamics +// Copyright (c) 2021-2026 Timothée David--Cléris +// 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 + class LifetimeTracker : public shambase::WithUUID, 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() { + 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; + + 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