Common System 0.2.0
Common interfaces and patterns for system integration
Loading...
Searching...
No Matches
kcenon::common::interfaces::health_dependency_graph Class Reference

Manages dependencies between health checks as a DAG. More...

#include <health_dependency_graph.h>

Collaboration diagram for kcenon::common::interfaces::health_dependency_graph:
Collaboration graph

Public Member Functions

 health_dependency_graph ()=default
 
 ~health_dependency_graph ()=default
 
 health_dependency_graph (const health_dependency_graph &)=delete
 
health_dependency_graphoperator= (const health_dependency_graph &)=delete
 
 health_dependency_graph (health_dependency_graph &&)=delete
 
health_dependency_graphoperator= (health_dependency_graph &&)=delete
 
Result< bool > add_node (const std::string &name, std::shared_ptr< health_check > check)
 Add a health check node to the graph.
 
Result< bool > remove_node (const std::string &name)
 Remove a health check node from the graph.
 
Result< bool > add_dependency (const std::string &dependent, const std::string &dependency)
 Add a dependency between two nodes.
 
Result< bool > remove_dependency (const std::string &dependent, const std::string &dependency)
 Remove a dependency between two nodes.
 
std::set< std::string > get_dependencies (const std::string &name) const
 Get all dependencies of a node.
 
std::set< std::string > get_dependents (const std::string &name) const
 Get all nodes that depend on a given node.
 
bool would_create_cycle (const std::string &from, const std::string &to) const
 Check if adding a dependency would create a cycle.
 
Result< std::vector< std::string > > topological_sort () const
 Get topological sort of all nodes.
 
Result< health_check_resultcheck_with_dependencies (const std::string &name)
 Execute health check with its dependencies.
 
std::set< std::string > get_failure_impact (const std::string &name) const
 Get the impact of a node failure.
 
bool has_node (const std::string &name) const
 Check if a node exists.
 
std::size_t size () const
 Get number of nodes.
 
bool empty () const
 Check if graph is empty.
 
void clear ()
 Clear all nodes and dependencies.
 
std::vector< std::string > get_all_nodes () const
 Get all node names.
 

Private Member Functions

bool would_create_cycle_internal (const std::string &from, const std::string &to) const
 
Result< health_check_resultcheck_with_dependencies_internal (const std::string &name, std::unordered_map< std::string, health_check_result > &results)
 

Private Attributes

std::unordered_map< std::string, std::shared_ptr< health_check > > nodes_
 
std::unordered_map< std::string, std::set< std::string > > dependencies_
 
std::unordered_map< std::string, std::set< std::string > > dependents_
 
std::mutex mutex_
 

Detailed Description

Manages dependencies between health checks as a DAG.

This class allows defining dependencies between health checks and executing them in the correct order. It supports cycle detection and topological sorting for proper execution order.

Example usage:

graph.add_node("database", db_check);
graph.add_node("cache", cache_check);
graph.add_node("api", api_check);
// api depends on database and cache
graph.add_dependency("api", "database");
graph.add_dependency("api", "cache");
// Execute in topological order
auto order = graph.topological_sort();
for (const auto& name : order.value()) {
auto result = graph.check_with_dependencies(name);
}
Manages dependencies between health checks as a DAG.
Result< bool > add_dependency(const std::string &dependent, const std::string &dependency)
Add a dependency between two nodes.
Result< bool > add_node(const std::string &name, std::shared_ptr< health_check > check)
Add a health check node to the graph.
Result< health_check_result > check_with_dependencies(const std::string &name)
Execute health check with its dependencies.
Result< std::vector< std::string > > topological_sort() const
Get topological sort of all nodes.

Definition at line 54 of file health_dependency_graph.h.

Constructor & Destructor Documentation

◆ health_dependency_graph() [1/3]

kcenon::common::interfaces::health_dependency_graph::health_dependency_graph ( )
default

◆ ~health_dependency_graph()

kcenon::common::interfaces::health_dependency_graph::~health_dependency_graph ( )
default

◆ health_dependency_graph() [2/3]

kcenon::common::interfaces::health_dependency_graph::health_dependency_graph ( const health_dependency_graph & )
delete

◆ health_dependency_graph() [3/3]

kcenon::common::interfaces::health_dependency_graph::health_dependency_graph ( health_dependency_graph && )
delete

Member Function Documentation

◆ add_dependency()

Result< bool > kcenon::common::interfaces::health_dependency_graph::add_dependency ( const std::string & dependent,
const std::string & dependency )
inline

Add a dependency between two nodes.

Parameters
dependentThe node that depends on another
dependencyThe node being depended upon
Returns
Result indicating success or failure

Definition at line 124 of file health_dependency_graph.h.

124 {
125 std::lock_guard<std::mutex> lock(mutex_);
126
127 if (nodes_.find(dependent) == nodes_.end()) {
128 return {error_info{1, "Dependent node not found: " + dependent,
129 "health_dependency_graph"}};
130 }
131 if (nodes_.find(dependency) == nodes_.end()) {
132 return {error_info{2, "Dependency node not found: " + dependency,
133 "health_dependency_graph"}};
134 }
135
136 if (would_create_cycle_internal(dependent, dependency)) {
137 return {error_info{3,
138 "Adding dependency would create a cycle: " + dependent + " -> " +
140 "health_dependency_graph"}};
141 }
142
143 dependencies_[dependent].insert(dependency);
144 dependents_[dependency].insert(dependent);
145
146 return ok(true);
147 }
bool would_create_cycle_internal(const std::string &from, const std::string &to) const
std::unordered_map< std::string, std::set< std::string > > dependencies_
std::unordered_map< std::string, std::shared_ptr< health_check > > nodes_
std::unordered_map< std::string, std::set< std::string > > dependents_
VoidResult ok()
Create a successful void result.
Definition utilities.h:71

References dependencies_, kcenon::common::interfaces::dependency, dependents_, mutex_, nodes_, kcenon::common::ok(), and would_create_cycle_internal().

Referenced by kcenon::common::interfaces::health_monitor::add_dependency().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_node()

Result< bool > kcenon::common::interfaces::health_dependency_graph::add_node ( const std::string & name,
std::shared_ptr< health_check > check )
inline

Add a health check node to the graph.

Parameters
nameUnique name for this node
checkHealth check implementation
Returns
Result indicating success or failure

Definition at line 70 of file health_dependency_graph.h.

70 {
71 if (name.empty()) {
72 return {error_info{1, "Node name cannot be empty", "health_dependency_graph"}};
73 }
74 if (!check) {
75 return {error_info{2, "Health check cannot be null", "health_dependency_graph"}};
76 }
77
78 std::lock_guard<std::mutex> lock(mutex_);
79
80 if (nodes_.find(name) != nodes_.end()) {
81 return {error_info{3, "Node already exists: " + name, "health_dependency_graph"}};
82 }
83
84 nodes_[name] = std::move(check);
85 dependencies_[name] = {};
86 dependents_[name] = {};
87
88 return ok(true);
89 }

References dependencies_, dependents_, mutex_, nodes_, and kcenon::common::ok().

Referenced by kcenon::common::interfaces::health_monitor::register_check().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ check_with_dependencies()

Result< health_check_result > kcenon::common::interfaces::health_dependency_graph::check_with_dependencies ( const std::string & name)
inline

Execute health check with its dependencies.

Parameters
nameName of the node to check
Returns
Result containing the health check result or error

This method first checks all dependencies recursively. If any dependency fails with unhealthy status, the dependent check is marked as unhealthy without executing it.

Definition at line 271 of file health_dependency_graph.h.

271 {
272 std::lock_guard<std::mutex> lock(mutex_);
273
274 auto node_it = nodes_.find(name);
275 if (node_it == nodes_.end()) {
276 return {error_info{1, "Node not found: " + name, "health_dependency_graph"}};
277 }
278
279 std::unordered_map<std::string, health_check_result> results;
280 return check_with_dependencies_internal(name, results);
281 }
Result< health_check_result > check_with_dependencies_internal(const std::string &name, std::unordered_map< std::string, health_check_result > &results)

References check_with_dependencies_internal(), mutex_, and nodes_.

Referenced by kcenon::common::interfaces::health_monitor::check(), and kcenon::common::interfaces::health_monitor::refresh().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ check_with_dependencies_internal()

Result< health_check_result > kcenon::common::interfaces::health_dependency_graph::check_with_dependencies_internal ( const std::string & name,
std::unordered_map< std::string, health_check_result > & results )
inlineprivate

Definition at line 400 of file health_dependency_graph.h.

402 {
403 // Check if already computed
404 auto result_it = results.find(name);
405 if (result_it != results.end()) {
406 return ok(result_it->second);
407 }
408
409 auto node_it = nodes_.find(name);
410 if (node_it == nodes_.end()) {
411 return {error_info{1, "Node not found: " + name, "health_dependency_graph"}};
412 }
413
414 // Check all dependencies first
415 bool dependency_failed = false;
416 std::string failure_reason;
417
418 for (const auto& dep : dependencies_[name]) {
419 auto dep_result = check_with_dependencies_internal(dep, results);
420 if (dep_result.is_err()) {
421 dependency_failed = true;
422 failure_reason = "Dependency check failed: " + dep;
423 break;
424 }
425
426 if (dep_result.value().status == health_status::unhealthy) {
427 dependency_failed = true;
428 failure_reason = "Dependency unhealthy: " + dep;
429 break;
430 }
431 }
432
433 health_check_result result;
434 if (dependency_failed) {
435 result.status = health_status::unhealthy;
436 result.message = failure_reason;
437 result.metadata["skipped"] = "true";
438 result.metadata["reason"] = "dependency_failure";
439 } else {
440 result = node_it->second->check();
441 }
442
443 results[name] = result;
444 return ok(result);
445 }

References check_with_dependencies_internal(), dependencies_, kcenon::common::interfaces::health_check_result::message, kcenon::common::interfaces::health_check_result::metadata, nodes_, kcenon::common::ok(), kcenon::common::interfaces::health_check_result::status, and kcenon::common::interfaces::unhealthy.

Referenced by check_with_dependencies(), and check_with_dependencies_internal().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ clear()

void kcenon::common::interfaces::health_dependency_graph::clear ( )
inline

Clear all nodes and dependencies.

Definition at line 344 of file health_dependency_graph.h.

344 {
345 std::lock_guard<std::mutex> lock(mutex_);
346 nodes_.clear();
347 dependencies_.clear();
348 dependents_.clear();
349 }

References dependencies_, dependents_, mutex_, and nodes_.

◆ empty()

bool kcenon::common::interfaces::health_dependency_graph::empty ( ) const
inlinenodiscard

Check if graph is empty.

Returns
true if no nodes exist

Definition at line 336 of file health_dependency_graph.h.

336 {
337 std::lock_guard<std::mutex> lock(mutex_);
338 return nodes_.empty();
339 }

References mutex_, and nodes_.

◆ get_all_nodes()

std::vector< std::string > kcenon::common::interfaces::health_dependency_graph::get_all_nodes ( ) const
inlinenodiscard

Get all node names.

Returns
Vector of all node names

Definition at line 355 of file health_dependency_graph.h.

355 {
356 std::lock_guard<std::mutex> lock(mutex_);
357 std::vector<std::string> names;
358 names.reserve(nodes_.size());
359 for (const auto& [name, _] : nodes_) {
360 names.push_back(name);
361 }
362 return names;
363 }

References mutex_, and nodes_.

Referenced by kcenon::common::interfaces::health_monitor::get_check_names(), and kcenon::common::interfaces::health_monitor::refresh().

Here is the caller graph for this function:

◆ get_dependencies()

std::set< std::string > kcenon::common::interfaces::health_dependency_graph::get_dependencies ( const std::string & name) const
inlinenodiscard

Get all dependencies of a node.

Parameters
nameNode name
Returns
Set of dependency names

Definition at line 176 of file health_dependency_graph.h.

176 {
177 std::lock_guard<std::mutex> lock(mutex_);
178
179 auto it = dependencies_.find(name);
180 if (it != dependencies_.end()) {
181 return it->second;
182 }
183 return {};
184 }

References dependencies_, and mutex_.

◆ get_dependents()

std::set< std::string > kcenon::common::interfaces::health_dependency_graph::get_dependents ( const std::string & name) const
inlinenodiscard

Get all nodes that depend on a given node.

Parameters
nameNode name
Returns
Set of dependent names

Definition at line 191 of file health_dependency_graph.h.

191 {
192 std::lock_guard<std::mutex> lock(mutex_);
193
194 auto it = dependents_.find(name);
195 if (it != dependents_.end()) {
196 return it->second;
197 }
198 return {};
199 }

References dependents_, and mutex_.

◆ get_failure_impact()

std::set< std::string > kcenon::common::interfaces::health_dependency_graph::get_failure_impact ( const std::string & name) const
inlinenodiscard

Get the impact of a node failure.

Parameters
nameName of the failed node
Returns
Set of all nodes that would be affected by this failure

Definition at line 288 of file health_dependency_graph.h.

288 {
289 std::lock_guard<std::mutex> lock(mutex_);
290
291 std::set<std::string> impacted;
292 std::queue<std::string> to_visit;
293 to_visit.push(name);
294
295 while (!to_visit.empty()) {
296 std::string current = to_visit.front();
297 to_visit.pop();
298
299 auto it = dependents_.find(current);
300 if (it != dependents_.end()) {
301 for (const auto& dependent : it->second) {
302 if (impacted.find(dependent) == impacted.end()) {
303 impacted.insert(dependent);
304 to_visit.push(dependent);
305 }
306 }
307 }
308 }
309
310 return impacted;
311 }

References dependents_, and mutex_.

◆ has_node()

bool kcenon::common::interfaces::health_dependency_graph::has_node ( const std::string & name) const
inlinenodiscard

Check if a node exists.

Parameters
nameNode name
Returns
true if node exists

Definition at line 318 of file health_dependency_graph.h.

318 {
319 std::lock_guard<std::mutex> lock(mutex_);
320 return nodes_.find(name) != nodes_.end();
321 }

References mutex_, and nodes_.

Referenced by kcenon::common::interfaces::health_monitor::has_check().

Here is the caller graph for this function:

◆ operator=() [1/2]

health_dependency_graph & kcenon::common::interfaces::health_dependency_graph::operator= ( const health_dependency_graph & )
delete

◆ operator=() [2/2]

health_dependency_graph & kcenon::common::interfaces::health_dependency_graph::operator= ( health_dependency_graph && )
delete

◆ remove_dependency()

Result< bool > kcenon::common::interfaces::health_dependency_graph::remove_dependency ( const std::string & dependent,
const std::string & dependency )
inline

Remove a dependency between two nodes.

Parameters
dependentThe dependent node
dependencyThe dependency node
Returns
Result indicating success or failure

Definition at line 155 of file health_dependency_graph.h.

155 {
156 std::lock_guard<std::mutex> lock(mutex_);
157
158 if (nodes_.find(dependent) == nodes_.end()) {
159 return {error_info{1, "Dependent node not found: " + dependent,
160 "health_dependency_graph"}};
161 }
162
163 dependencies_[dependent].erase(dependency);
164 if (nodes_.find(dependency) != nodes_.end()) {
165 dependents_[dependency].erase(dependent);
166 }
167
168 return ok(true);
169 }

References dependencies_, kcenon::common::interfaces::dependency, dependents_, mutex_, nodes_, and kcenon::common::ok().

Here is the call graph for this function:

◆ remove_node()

Result< bool > kcenon::common::interfaces::health_dependency_graph::remove_node ( const std::string & name)
inline

Remove a health check node from the graph.

Parameters
nameName of the node to remove
Returns
Result indicating success or failure

Definition at line 96 of file health_dependency_graph.h.

96 {
97 std::lock_guard<std::mutex> lock(mutex_);
98
99 if (nodes_.find(name) == nodes_.end()) {
100 return {error_info{1, "Node not found: " + name, "health_dependency_graph"}};
101 }
102
103 // Remove all dependencies to/from this node
104 for (const auto& dep : dependencies_[name]) {
105 dependents_[dep].erase(name);
106 }
107 for (const auto& dep : dependents_[name]) {
108 dependencies_[dep].erase(name);
109 }
110
111 nodes_.erase(name);
112 dependencies_.erase(name);
113 dependents_.erase(name);
114
115 return ok(true);
116 }

References dependencies_, dependents_, mutex_, nodes_, and kcenon::common::ok().

Referenced by kcenon::common::interfaces::health_monitor::unregister_check().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ size()

std::size_t kcenon::common::interfaces::health_dependency_graph::size ( ) const
inlinenodiscard

Get number of nodes.

Returns
Number of nodes in the graph

Definition at line 327 of file health_dependency_graph.h.

327 {
328 std::lock_guard<std::mutex> lock(mutex_);
329 return nodes_.size();
330 }

References mutex_, and nodes_.

◆ topological_sort()

Result< std::vector< std::string > > kcenon::common::interfaces::health_dependency_graph::topological_sort ( ) const
inlinenodiscard

Get topological sort of all nodes.

Returns
Result containing sorted node names or error if cycle exists

Definition at line 216 of file health_dependency_graph.h.

216 {
217 std::lock_guard<std::mutex> lock(mutex_);
218
219 std::unordered_map<std::string, int> in_degree;
220 for (const auto& [name, _] : nodes_) {
221 in_degree[name] = 0;
222 }
223
224 for (const auto& [name, deps] : dependencies_) {
225 for (const auto& dep : deps) {
226 (void)dep;
227 }
228 in_degree[name] = static_cast<int>(deps.size());
229 }
230
231 std::queue<std::string> zero_in_degree;
232 for (const auto& [name, degree] : in_degree) {
233 if (degree == 0) {
234 zero_in_degree.push(name);
235 }
236 }
237
238 std::vector<std::string> result;
239 result.reserve(nodes_.size());
240
241 while (!zero_in_degree.empty()) {
242 std::string current = zero_in_degree.front();
243 zero_in_degree.pop();
244 result.push_back(current);
245
246 for (const auto& dependent : dependents_.at(current)) {
247 --in_degree[dependent];
248 if (in_degree[dependent] == 0) {
249 zero_in_degree.push(dependent);
250 }
251 }
252 }
253
254 if (result.size() != nodes_.size()) {
255 return {error_info{1, "Cycle detected in dependency graph",
256 "health_dependency_graph"}};
257 }
258
259 return ok(result);
260 }

References dependencies_, dependents_, mutex_, nodes_, and kcenon::common::ok().

Here is the call graph for this function:

◆ would_create_cycle()

bool kcenon::common::interfaces::health_dependency_graph::would_create_cycle ( const std::string & from,
const std::string & to ) const
inlinenodiscard

Check if adding a dependency would create a cycle.

Parameters
fromSource node
toTarget node
Returns
true if a cycle would be created

Definition at line 207 of file health_dependency_graph.h.

207 {
208 std::lock_guard<std::mutex> lock(mutex_);
209 return would_create_cycle_internal(from, to);
210 }

References mutex_, and would_create_cycle_internal().

Here is the call graph for this function:

◆ would_create_cycle_internal()

bool kcenon::common::interfaces::health_dependency_graph::would_create_cycle_internal ( const std::string & from,
const std::string & to ) const
inlinenodiscardprivate

Definition at line 366 of file health_dependency_graph.h.

367 {
368 // Adding "from depends on to" edge
369 // This would create a cycle if 'from' is reachable from 'to' via dependencies
370 // i.e., to -> ... -> from already exists in the dependency chain
371 std::unordered_set<std::string> visited;
372 std::queue<std::string> queue;
373 queue.push(to);
374
375 while (!queue.empty()) {
376 std::string current = queue.front();
377 queue.pop();
378
379 if (current == from) {
380 return true;
381 }
382
383 if (visited.find(current) != visited.end()) {
384 continue;
385 }
386 visited.insert(current);
387
388 // Follow the dependencies of the current node
389 auto it = dependencies_.find(current);
390 if (it != dependencies_.end()) {
391 for (const auto& dep : it->second) {
392 queue.push(dep);
393 }
394 }
395 }
396
397 return false;
398 }

References dependencies_.

Referenced by add_dependency(), and would_create_cycle().

Here is the caller graph for this function:

Member Data Documentation

◆ dependencies_

std::unordered_map<std::string, std::set<std::string> > kcenon::common::interfaces::health_dependency_graph::dependencies_
private

◆ dependents_

std::unordered_map<std::string, std::set<std::string> > kcenon::common::interfaces::health_dependency_graph::dependents_
private

◆ mutex_

◆ nodes_

std::unordered_map<std::string, std::shared_ptr<health_check> > kcenon::common::interfaces::health_dependency_graph::nodes_
private

The documentation for this class was generated from the following file: