[libcamera-devel] [PATCH 2/2] libcamera: base: log: Fix LogCategory creation issues
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Mon Aug 29 16:16:54 CEST 2022
Hi Tomi,
On Mon, Aug 29, 2022 at 11:18:29AM +0300, Tomi Valkeinen wrote:
> On 27/08/2022 04:27, Laurent Pinchart wrote:
> > On Fri, Aug 26, 2022 at 02:39:31PM +0300, Tomi Valkeinen via libcamera-devel wrote:
> >> Each declaration of a LogCategory will create a new LogCategory, and
> >> will be stored in an unordered_set Logger::categories_. This means that
> >> when a plugin .so is unloaded and loaded, as happens when destructing
> >> and creating a CamereManager, we'll get duplicate categories.
> >>
> >> The Logger::registerCategory docs say "Log categories must have unique
> >> names. If a category with the same name already exists this function
> >> performs no operation.". The code does not comply with this.
> >>
> >> We solve the issue with two changes:
> >>
> >> Change the unordered_set to a vector for simplicity, as there's no need
> >> for an unordered_set.
> >>
> >> Instead of using the LogCategory constructor to create new categories in
> >> _LOG_CATEGORY() macro, use a factory method. The factory method will
> >> return either an existing LogCategory if one exists with the given name,
> >> or a newly created one.
> >>
> >> Signed-off-by: Tomi Valkeinen <tomi.valkeinen at ideasonboard.com>
> >> ---
> >> include/libcamera/base/log.h | 6 +++--
> >> src/libcamera/base/log.cpp | 51 +++++++++++++++++++++++++++++++-----
> >> 2 files changed, 48 insertions(+), 9 deletions(-)
> >>
> >> diff --git a/include/libcamera/base/log.h b/include/libcamera/base/log.h
> >> index 8b462767..76e01118 100644
> >> --- a/include/libcamera/base/log.h
> >> +++ b/include/libcamera/base/log.h
> >> @@ -29,7 +29,7 @@ enum LogSeverity {
> >> class LogCategory
> >> {
> >> public:
> >> - explicit LogCategory(const char *name);
> >> + static LogCategory *create(const std::string &name);
> >
> > Let's pass a const char * to this function, to have a single
> > construction of a std::string inside the function instead of in every
> > caller.
>
> Well, I think we should never use char * in C++ code unless absolutely
> necessary as we have a proper class for string, which makes it much
> harder to write buggy code wrt. string operations. Here, I think that's
> a bit of a pointless optimization considering the amount of times these
> functions are called.
>
> Nevertheless, a better option compared to std::string (and, I think,
> char *) would be std::string_view. Is that fine for you?
It would, but I see you've kept const char * in v2 after falling into
the rabbit hole of comparing performances :-) We don't use
std::string_view in libcamera, but maybe we should consider it, I like
the idea of bundling the string pointer and size together, much like we
do in the Span class.
> >>
> >> const std::string &name() const { return name_; }
> >> LogSeverity severity() const { return severity_; }
> >> @@ -38,6 +38,8 @@ public:
> >> static const LogCategory &defaultCategory();
> >>
> >> private:
> >> + explicit LogCategory(const std::string &name);
> >
> > Is the change from const char * to std::string needed here ? If so, does
> > it belong to 1/2 ?
>
> I don't think the change from char * to string is needed in either
> patch. It just made sense to me to get rid of the char * use. It could
> be dropped or done before either patch.
>
> >> +
> >> const std::string name_;
> >> LogSeverity severity_;
> >> };
> >> @@ -49,7 +51,7 @@ extern const LogCategory &_LOG_CATEGORY(name)();
> >> const LogCategory &_LOG_CATEGORY(name)() \
> >> { \
> >> /* The instance will be deleted by the Logger destructor. */ \
> >> - static LogCategory *category = new LogCategory(#name); \
> >> + static LogCategory *category = LogCategory::create(#name); \
> >> return *category; \
> >> }
> >>
> >> diff --git a/src/libcamera/base/log.cpp b/src/libcamera/base/log.cpp
> >> index a4a5b452..c6500e96 100644
> >> --- a/src/libcamera/base/log.cpp
> >> +++ b/src/libcamera/base/log.cpp
> >> @@ -314,10 +314,11 @@ private:
> >>
> >> friend LogCategory;
> >> void registerCategory(LogCategory *category);
> >> + LogCategory *findCategory(const std::string &name) const;
> >>
> >> static bool destroyed_;
> >>
> >> - std::unordered_set<LogCategory *> categories_;
> >> + std::vector<LogCategory *> categories_;
> >> std::list<std::pair<std::string, LogSeverity>> levels_;
> >>
> >> std::shared_ptr<LogOutput> output_;
> >> @@ -707,12 +708,14 @@ LogSeverity Logger::parseLogLevel(const std::string &level)
> >> * \brief Register a log category with the logger
> >> * \param[in] category The log category
> >> *
> >> - * Log categories must have unique names. If a category with the same name
> >> - * already exists this function performs no operation.
> >> + * Log categories must have unique names. It is illegal to call this function
> >
> > Nobody will arrest you for doing so, so you can write invalid instead of
> > illegal :-)
>
> Are you sure? ... Well, ok...
>
> >> + * if a log category with the same name already exists.
> >> */
> >> void Logger::registerCategory(LogCategory *category)
> >> {
> >> - categories_.insert(category);
> >> + ASSERT(!findCategory(category->name()));
> >
> > I wonder if this is overkill, as the caller checks that the category
> > isn't found first. I suppose the performance penalty of the lookup is
> > acceptable in debug mode, but maybe it's not needed ?
>
> I can drop this. The function is called only inside this file, so I
> think we can expect the parameter to be correct.
>
> >> +
> >> + categories_.push_back(category);
> >
> > This reminds me we need to add locking to the logger. That's out of
> > scope for this patch of course.
> >
> >>
> >> const std::string &name = category->name();
> >> for (const std::pair<std::string, LogSeverity> &level : levels_) {
> >> @@ -736,6 +739,21 @@ void Logger::registerCategory(LogCategory *category)
> >> }
> >> }
> >>
> >> +/**
> >> + * \brief Find an existing log category with the given name
> >> + * \param[in] name The name of the log category
> >
> > * \return ...
>
> Ok.
>
> >> + */
> >> +LogCategory *Logger::findCategory(const std::string &name) const
> >> +{
> >> + if (auto it = std::find_if(categories_.begin(), categories_.end(),
> >> + [&name](auto c) { return name == c->name(); });
> >
> > Would it be more efficient to store categories in an unordered_map
> > indexed by the category name ?
>
> Depends what you mean with efficient... =)
>
> I think we'll usually have some tens of entries. My pure guess is that
> the memory use with unordered_map would be much larger than with a
> vector, and we would be constructing string instances for the keys. The
> lookup difference would probably be unnoticeable with these amount of
> entries. The for loops we have that iterate over the categories_ would
> probably be slower with an unordered_map.
>
> And considering we'll call this function a few tens of times, I again
> think trying to optimize this is an unnecessary optimization.
>
> I think a more valid question is if an unordered_map or map would suit
> better code-wise (readability, maintainability) for this case. With the
> current uses of 'categories_', I don't see a big difference either way.
> Using a map would simplify this lookup, but iterating with for would be
> a bit more complex.
OK.
> >> + it != categories_.end()) {
> >> + return *it;
> >> + }
> >> +
> >> + return nullptr;
> >> +}
> >> +
> >> /**
> >> * \enum LogSeverity
> >> * Log message severity
> >> @@ -760,14 +778,33 @@ void Logger::registerCategory(LogCategory *category)
> >> * and is used to control the log level per group.
> >> */
> >>
> >> +/**
> >> + * \brief Create a new LogCategory or return an existing one
> >
> > * \param[in] name ...
>
> Ok.
>
> >> + * \return The pointer to the LogCategory
> >
> > The return documentation goes to the end of the comment.
>
> Ok.
>
> >> + *
> >> + * Create and return a new LogCategory with the given name if such a category
> >> + * does not yet exist, or return the existing one.
> >> + */
> >> +LogCategory *LogCategory::create(const std::string &name)
> >> +{
> >> + LogCategory *category = Logger::instance()->findCategory(name);
> >> +
> >> + if (!category) {
> >> + category = new LogCategory(name);
> >> +
> >
> > You could drop the blank line.
>
> Ok.
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list