-
Notifications
You must be signed in to change notification settings - Fork 158
[QC-1298] Extensible interface for accessing QC data sources #2589
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
Conversation
6b6734d to
c37c37e
Compare
|
@knopers8 version 2. I am not saying that it is 100% done, but it should fulfill everything we talked about, so you can take a look as most of the functionality is there. |
|
For the record, we discussed the PR in person and agreed to extend the interface adoption to Aggregators and add support for inserting raw pointers. I will do a full review once we have these two points complete. |
knopers8
left a comment
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.
Thanks a lot! Indeed I am not happy that we leak some data storage details from Check and Aggregator into Data, but it seems we cannot avoid it. Otherwise, the interface is looking very promising.
I would ask you for the following:
- See what could be moved to compiled source files. This interface will be included in hundreds of files, so it should be well optimized for compilation time. Consider having compiled versions for MonitorObject and QualityObject specializations.
- See what could be hidden from the main header file into an
.inlfor easier reading by the users. - Prepare helpers (and feel free to disagree with the proposals):
iterateMonitorObjects()iterateMonitorObjects(std::string_view taskName)getMonitorObject<StoredType = MonitorObject>(std::string_view objectName, std::string_view taskName)// no risk of obj name collisionsgetMonitorObject<StoredType = MonitorObject>(std::string_view objectName)// gets first matching MO!!!- `getMonitorObject(std::string_view objectName) // gets the underlying object if StoredType is anything else than MonitorObject
getMonitorObject<StoredType>(std::string_view objectName, std::string_view taskName)// like above, but no risk of obj name collisions- similar helpers for QOs
- to be discussed also with Barth: maybe these helpers should be a part of
Data. the advantage is that users won't have to passDataas an argument. The disadvantage is polluting the interface...
|
okay, thanks I will add the helpers and address other necessary additions. |
5597c28 to
a0ae926
Compare
knopers8
left a comment
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.
Thanks, just a few more suggestions, but this looks already good.
I propose that on Tuesday QC weekly we brainstorm the three of us:
- interface names
- whether to move helpers to
Dataor keep it inDataAdapters - whether returning
std::optional<std::reference_wrapper>is the easiest and safest we can do for the users.
@Barthelemy Could you perhaps have a look as well?
| template <typename T> | ||
| static constexpr auto any_to_specific = std::views::transform([](const auto& pair) -> std::pair<std::string_view, const T*> { return { pair.first, any_cast_try_shared_raw_ptr<T>(pair.second) }; }); | ||
|
|
||
| static constexpr auto filter_nullptr_in_pair = std::views::filter([](const auto& pair) -> bool { return pair.second != nullptr; }); | ||
|
|
||
| static constexpr auto filter_nullptr = std::views::filter([](const auto* ptr) -> bool { return ptr != nullptr; }); | ||
|
|
||
| static constexpr auto pair_to_reference = std::views::transform([](const auto& pair) -> const auto& { return *pair.second; }); | ||
|
|
||
| static constexpr auto pair_to_value = std::views::transform([](const auto& pair) -> const auto* { return pair.second; }); | ||
|
|
||
| static constexpr auto pointer_to_reference = std::views::transform([](const auto* ptr) -> const auto& { return *ptr; }); |
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.
consider breaking into multiple lines for easier readability, e.g.
template <typename T>
static constexpr auto any_to_specific = std::views::transform(
[](const auto& pair) -> std::pair<std::string_view, const T*> {
return { pair.first, any_cast_try_shared_raw_ptr<T>(pair.second) };
});
|
|
||
| static constexpr auto filter_nullptr = std::views::filter([](const auto* ptr) -> bool { return ptr != nullptr; }); | ||
|
|
||
| static constexpr auto pair_to_reference = std::views::transform([](const auto& pair) -> const auto& { return *pair.second; }); |
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.
| static constexpr auto pair_to_reference = std::views::transform([](const auto& pair) -> const auto& { return *pair.second; }); | |
| static constexpr auto pair_to_value_const_ref = std::views::transform([](const auto& pair) -> const auto& { return *pair.second; }); |
| return result; | ||
| } | ||
|
|
||
| const TH1& histogram = histOpt.value().get(); |
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.
I have to admit I am not a great fan of .value().get(), but I don't see a better option at the moment than std::reference_wrapper. This being said, here it should automatically cast to const TH1& without get() and if we go this way, the examples should use implicit casting.
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.
I agree
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.
I agree with the implicit operator... I was using explicit here for 2 reasons: I overlooked implicit operator in docs and I personally prefer to use explicit calls, so I am not surprised how something happened (eg. std::optional.has_value(), .value(), or comparing raw pointers to nullptr instead of just using !)...
One thing that I would like to ask is: should I use explicit type (const TH1& in this case), or const auto&? What do you think is better for the example for users?
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.
according to my trials on godbolt, const auto& was not enough to invoke implicit cast, so I think we have to use const TH1&. Perhaps the first case it is used in SkeletonCheck and SkeletonAggregator should be annotated with a comment that we are in fact casting from a reference wrapper.
| template <typename StoredType = MonitorObject> | ||
| std::optional<std::reference_wrapper<const StoredType>> getMonitorObject(const Data& data, std::string_view objectName); | ||
|
|
||
| inline auto iterateQualityObjects(const Data& data); |
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.
i think we need also inline auto iterateQualityObjects(const Data& data, std::string_view checkName);. One Check may return several QOs if the policy OnEachSeparately is selected.
Barthelemy
left a comment
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.
Thank you, this is very nice. Really great !
I have put a few comments and we will discuss it during this afternoon's meeting.
In general, I would appreciate if there were more comments to explain what the intent is and why it is done this or that way.
I am also pondering the benefit of having a separate file for the inline stuff.
|
|
||
| // Override interface | ||
| void configure() override; | ||
| Quality check(std::map<std::string, std::shared_ptr<MonitorObject>>* moMap) override; |
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.
why have both check methods ?
Do we want users to have both ?
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.
Just to clarify what I mean: I would expect users to only implement the check that takes data if they write a new Check or if they want to move to the new interface.
And if they don't want to do anything, then they just keep the old implementation.
| return result; | ||
| } | ||
|
|
||
| const TH1& histogram = histOpt.value().get(); |
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.
I agree
| // or submit itself to any jurisdiction. | ||
|
|
||
| /// | ||
| /// \file Data.h |
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.
That's a very generic name
| concept invocable_r = std::invocable<Function, Args...> && std::same_as<std::invoke_result_t<Function, Args...>, Result>; | ||
|
|
||
| template <typename ContainerMap> | ||
| class DataGeneric |
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.
Perhaps you could have a comment to explain what its purpose is and how it fits in the QC .
| } | ||
| }; | ||
|
|
||
| using transparent_unordered_map = std::unordered_map<std::string, std::any, StringHash, std::equal_to<>>; |
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.
A bit of explanation of what we are doing here would be helpful.
| // or submit itself to any jurisdiction. | ||
|
|
||
| /// | ||
| /// \file Data.inl |
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.
As for Data.h, it would be helpful to have a bit more comments about the goal and intent of these functions.
I will add more comments and documentation about everything when the code is finalised. Neither me nor Piotr were sure about naming, so if you don't like some naming go ahead and propose better names, this was built as a POC, so naming might be all over the place as it was being changed as I went along and I didn't make "finalising pass" to check if the names are as best as they can be
Do you mean .inl files? |
@Barthelemy I asked Michal to do it and the reason was to make this header as much readable as possible for the users, so they are not lost in the implementation details when checking how they can use the interface. |
@knopers8 ok, understood. |
knopers8
left a comment
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.
I am wondering if we still need QCInputsGeneric, now that you found the most suitable container and it's unlikely we would change it or use more than one. What do you think about merging it back with QCInputs to simplify the code?
| virtual ~CheckInterface() = default; | ||
|
|
||
| /// \brief Returns the quality associated with these objects. | ||
| /// |
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.
please add a note that users should prefer the other one and this one is for backwards-compatibility.
| /// \returns Optional reference to const Result if found desired item of type Result. | ||
| /// \par Example | ||
| /// \code{.cpp} | ||
| /// if (auto opt = data.get<unsigned>("count")) { |
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.
i would propose to use MonitorObject in examples so they are more real-life.
| /// \returns Range of const references to stored Result instances. | ||
| /// \par Example | ||
| /// \code{.cpp} | ||
| /// for (auto& val : data.iterateByResultype<unsigned>()) { |
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.
the example uses a different method name
| /// \brief Transparent hash functor for string and string_view. | ||
| /// | ||
| /// Enables heterogeneous lookup in unordered maps keyed by std::string. | ||
| struct StringHash { |
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.
Perhaps this could be moved to inl as well? struct StringHash; declaration might be only needed.
| inline auto iterateMonitorObjects(const QCInputs& data, std::string_view taskName); | ||
|
|
||
| /// \brief Retrieve the first MonitorObject of type StoredType matching both name and task. | ||
| /// \tparam StoredType Type of MonitorObject or subclass to retrieve. |
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.
"subclass" typically means "derived class", how about "stored class" or "wrapped class"?
There are other uses of "subclass" in this file".
| inline auto iterateQualityObjects(const QCInputs& data, std::string_view checkName) | ||
| { | ||
| const auto filterQOByName = [checkName](const auto& pair) { | ||
| return std::string_view(pair.second->getName()) == checkName; |
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.
Use getCheckName. getName can be different than getCheckName when OnEachSeparately update policy was used for a Check. In such case, a separate QO is produced for each MO that is an input, so you get CheckA/MO1, Check/MO2, ... See the implementation of QualityObject::getName
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.
I was looking at it and I thought that for some reason you want to specifically use getName and not getCheckName... that is my bad, sorry
| namespace o2::quality_control::core | ||
| { | ||
|
|
||
| std::optional<std::reference_wrapper<const QualityObject>> getQualityObject(const QCInputs& data, std::string_view objectName) |
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.
The declaration mentions checkName as the argument, here it's objectName. These might not be the same as mentioned earlier. Consequently, in the implementation, use getCheckName.
| for (const auto& item : qoMap) { | ||
| ILOG(Info, Devel) << "Object: " << (*item.second) << ENDM; | ||
| } | ||
| ILOG(Info, Devel) << " received a data of size : " << data.size() << ENDM; |
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.
| ILOG(Info, Devel) << " received a data of size : " << data.size() << ENDM; | |
| ILOG(Info, Devel) << " received data of size : " << data.size() << ENDM; |
Data is plural of datum.
Problem with that is that I would need to delete the benchmark code as well, which would mean that the reason for |
I think you can leave the benchmark for the selected container and add a comment next to |
knopers8
left a comment
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.
Thank you!
This is just first version of Data structure, that should be able to ingest everything. @knopers8 if you can take a look and say whether it is at least something to build upon.
Usage can be seen in
SkeletonCheck.cxx