Conversation
|
@matemat13 this is your child, so please check it and let me know if those changes make sense for you :) |
eb240db to
73e27fc
Compare
matemat13
left a comment
There was a problem hiding this comment.
looks good to me from perspective of functionality, but the documentation needs clearing up. There is the question of how to handle loops in find_roots() and find_error_roots() as rn it's inconsistent with find_dependency_roots() if I'm not mistaken
| return std::all_of(std::begin(errors), std::end(errors), [](const auto& error) { return error.is_waiting_for() || error.is_no_error(); }) | ||
| && std::any_of(std::begin(errors), std::end(errors), [](const auto& error) { return error.is_waiting_for(); }); |
There was a problem hiding this comment.
I'd split these two lines to two named booleans to make it clearer what each one of them actually checks for. Something like
// true iff there are no "raw" errors generated directly by this node
const bool no_raw_errors = std::all_of(std::begin(errors), std::end(errors), [](const auto& error) { return error.is_waiting_for() || error.is_no_error(); });
// true iff there is at least one waiting_for error (to rule out a node with all errors being no_error)
const bool at_least_one_waiting_for = std::any_of(std::begin(errors), std::end(errors), [](const auto& error) { return error.is_waiting_for(); });
return no_raw_errors && at_least_one_waiting_for;Although it avoids the early return of the && if the first part of the condition is met, this is only relevant for non-optimized builds, and it's worth the readability improvement IMO.
There was a problem hiding this comment.
Done in d323633, using your suggested names no_raw_errors / at_least_one_waiting_for.
|
|
||
| void build_graph(); | ||
|
|
||
| std::vector<const element_t*> DFS(element_t* from, bool* loop_detected_out = nullptr); |
There was a problem hiding this comment.
may be worth documenting even though it is private... it's a bit unclear what the returned value actually is. It's interpretation as the "roots" seems to be a bit different than that one in find_error_roots() and find_roots(), so it's worth clearing up IMO.
There was a problem hiding this comment.
Documented in 7f089d8. The doc block now explains what the returned "roots" are: every visited element with a genuine error, plus every member of a genuine dependency loop. It also explains how that feeds find_dependency_roots() directly, and find_roots()/find_error_roots() through element_t::loop_root. There's also a note on how a real back-edge is told apart from a diamond-shaped graph where paths rejoin. That was a separate bug, fixed in 4102868: it used to be reported as a loop.
| std::vector<element_info_t> find_dependency_roots(const node_id_t& node_id, bool* loop_detected_out = nullptr); | ||
|
|
||
| /** | ||
| * \brief Find all root-cause elements across the entire graph. |
There was a problem hiding this comment.
I'd update the documentation to make it clear how it's different from find_roots().
There was a problem hiding this comment.
also, how does this handle loops? I don't remember and it's not documented. If I'm reading the code correctly, loops are not reported by this method.
There was a problem hiding this comment.
Clarified in the doc. find_error_roots() is find_roots() minus healthy (no-error, actively reporting) elements that nothing waits on, so uninteresting leaves aren't reported as root causes.
Also, you were right, pure loops disappeared completely. Fixed in 7f089d8. DFS() now marks every member of a genuine loop with loop_root, and find_error_roots() includes those members. Covered by find_error_roots_pure_loop and find_error_roots_pure_loop_with_no_error_entry tests now.
| // an element is a root if it doesn't depend on anything else, or if it carries a genuine | ||
| // error of its own even while also waiting for something else (that error would otherwise | ||
| // be silently skipped as the DFS walks past it toward its dependency) | ||
| if (!cur_elem->is_only_waiting_for()) |
There was a problem hiding this comment.
This comment seems to contradict R62, which also adds an element as a root if it's within a loop, which is not mentioned here. I'd add a mention here that that's handled later.
There was a problem hiding this comment.
Updated. The comment now says that loop members are added as roots further down, when the back-edge is found.
| * | ||
| * \return Copies of root element info as type-safe variants. | ||
| */ | ||
| std::vector<element_info_t> find_roots(); |
There was a problem hiding this comment.
Similar as the comment to R351 - I don't think this method handles loops, right? So it's behavior is different than that of find_dependency_roots(). Worth at least documenting, maybe even updating the implementation to handle that somehow...
There was a problem hiding this comment.
Same fix as for find_error_roots() (7f089d8). find_roots() now includes every loop member, which makes it consistent with find_dependency_roots(). Covered by find_roots_pure_loop test. In cb62198 I also tightened find_dependency_roots_detects_loop to require every member (it used to accept any non-empty result) and added a 3-element loop test.
| for (const auto& el_ptr : elements_) | ||
| { | ||
| // A leaf has no children (no one waits for it) | ||
| // A leaf has no parents, i.e. no one else in the graph waits for it |
…aiting-for entry find_error_roots(), find_roots(), and find_dependency_roots()/DFS() each excluded an element the moment it had any "waiting for" entry, even when that same element also carried a genuine error. DiagnosticsManager and its sensor-handler plugins share one ErrorPublisher identity, so a real, persistent plugin error could be silently dropped from the reported root-error set whenever a sibling plugin happened to report a normal startup "waiting for topic" at the same time. Add element_t::is_only_waiting_for(), true only when every non-"no error" entry is a "waiting for" dependency, and use it in place of the any-based is_waiting_for() check in all three root-finding functions. Also correct the docstrings/comments that described the wrong graph field or an error-content filter that didn't actually exist.
…_dot() Topic elements never go through add_element_from_msg() (only node elements do), so their stamp stays at its constructor default of 0 forever, and is_not_reporting() is hardcoded false for topics. write_dot() rendered this as "age: <seconds since epoch>s" for every topic vertex regardless of whether its expected publisher ever showed up. Skip the age/not-reporting line for topic elements instead.
73e27fc to
f552396
Compare
The combined all_of/any_of expression made it unclear what each half actually checked. Split it into two named booleans, no_raw_errors and at_least_one_waiting_for, per review feedback on PR ctu-mrs#118. No behavior change.
…loop in DFS() DFS()'s cycle detection only tracked a flat visited/unvisited flag, so reaching an element that had already been fully processed via some other, unrelated path -- e.g. a diamond-shaped dependency graph, or simply a later top-level walk in build_graph() reaching into an element an earlier one already finished -- was indistinguishable from a genuine back-edge to an ancestor on the current path. Both were reported as a loop. Track a third state (on_stack) so a loop is only reported when the revisited element is still an ancestor on the current DFS path. find_dependency_roots() no longer reports loop_detected = true or duplicates a root for an acyclic, reconverging graph.
…endency loops too Neither function had any notion of cycles, so a pure dependency loop with no genuine error anywhere in it was silently excluded entirely, unlike find_dependency_roots(). find_error_roots() is the function actually used in production, so a real stuck-dependency deadlock would report as "no errors". Mark every element on a detected cycle (loop_root) and include it in both functions' results, now that DFS()'s cycle detection is correct.
… loop member The 2-element loop test only checked that some root was returned. Require both members explicitly, add a 3-element loop regression test to confirm the loop marking works beyond the 2-element case, and document the one-root-per-member behavior in the header. No behavior change.
|
@matemat13 thanks! I think I've covered everything:
|
Summary
What changed:
waiting_forentry, even if it also had a genuine error. Newis_only_waiting_for()fixes this in all three root-finding functions.write_dot()topic age: topics never get astamp, so they showed a bogus age. That line is now skipped for topics.DFS(): a diamond-shaped graph was reported as a loop. Loops are now only reported for true back-edges (on_stack).find_roots()/find_error_roots()now report loop members (loop_root), andfind_dependency_roots()returns every member, not just one.Why:
DiagnosticsManagerplugins share oneErrorPublisheridentity, so a real error (e.g. GNSS init failure) disappeared from/uav1/root_errorswhenever a sibling reportedwaiting_for_topic.write_dot()topic age: misleading DOT output.DFS(): falseloop_detectedand duplicate roots.Impact
Errorgraph(include/mrs_lib/errorgraph/errorgraph.h,src/errorgraph/errorgraph.cpp,test/errorgraph/test.cpp)Testing status
What was tested:
How to repeat tests: