blob: f7c61bed52a318e4b576bdaed53e4fab6d0391d3 [file] [log] [blame]
// Copyright 2022 The Abseil Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "absl/log/internal/vlog_config.h"
#include <stddef.h>
#include <algorithm>
#include <atomic>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/base/const_init.h"
#include "absl/base/internal/spinlock.h"
#include "absl/base/no_destructor.h"
#include "absl/base/optimization.h"
#include "absl/base/thread_annotations.h"
#include "absl/log/internal/fnmatch.h"
#include "absl/memory/memory.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "absl/synchronization/mutex.h"
#include "absl/types/optional.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace log_internal {
namespace {
bool ModuleIsPath(absl::string_view module_pattern) {
#ifdef _WIN32
return module_pattern.find_first_of("/\\") != module_pattern.npos;
#else
return module_pattern.find('/') != module_pattern.npos;
#endif
}
} // namespace
bool VLogSite::SlowIsEnabled(int stale_v, int level) {
if (ABSL_PREDICT_TRUE(stale_v != kUninitialized)) {
// Because of the prerequisites to this function, we know that stale_v is
// either uninitialized or >= level. If it's not uninitialized, that means
// it must be >= level, thus we should log.
return true;
}
stale_v = log_internal::RegisterAndInitialize(this);
return ABSL_PREDICT_FALSE(stale_v >= level);
}
bool VLogSite::SlowIsEnabled0(int stale_v) { return SlowIsEnabled(stale_v, 0); }
bool VLogSite::SlowIsEnabled1(int stale_v) { return SlowIsEnabled(stale_v, 1); }
bool VLogSite::SlowIsEnabled2(int stale_v) { return SlowIsEnabled(stale_v, 2); }
bool VLogSite::SlowIsEnabled3(int stale_v) { return SlowIsEnabled(stale_v, 3); }
bool VLogSite::SlowIsEnabled4(int stale_v) { return SlowIsEnabled(stale_v, 4); }
bool VLogSite::SlowIsEnabled5(int stale_v) { return SlowIsEnabled(stale_v, 5); }
namespace {
struct VModuleInfo final {
std::string module_pattern;
bool module_is_path; // i.e. it contains a path separator.
int vlog_level;
// Allocates memory.
VModuleInfo(absl::string_view module_pattern_arg, bool module_is_path_arg,
int vlog_level_arg)
: module_pattern(std::string(module_pattern_arg)),
module_is_path(module_is_path_arg),
vlog_level(vlog_level_arg) {}
};
// `mutex` guards all of the data structures that aren't lock-free.
// To avoid problems with the heap checker which calls into `VLOG`, `mutex` must
// be a `SpinLock` that prevents fiber scheduling instead of a `Mutex`.
ABSL_CONST_INIT absl::base_internal::SpinLock mutex(
absl::kConstInit, absl::base_internal::SCHEDULE_KERNEL_ONLY);
// `GetUpdateSitesMutex()` serializes updates to all of the sites (i.e. those in
// `site_list_head`) themselves.
absl::Mutex* GetUpdateSitesMutex() {
// Chromium requires no global destructors, so we can't use the
// absl::kConstInit idiom since absl::Mutex as a non-trivial destructor.
static absl::NoDestructor<absl::Mutex> update_sites_mutex ABSL_ACQUIRED_AFTER(
mutex);
return update_sites_mutex.get();
}
ABSL_CONST_INIT int global_v ABSL_GUARDED_BY(mutex) = 0;
// `site_list_head` is the head of a singly-linked list. Traversal, insertion,
// and reads are atomic, so no locks are required, but updates to existing
// elements are guarded by `GetUpdateSitesMutex()`.
ABSL_CONST_INIT std::atomic<VLogSite*> site_list_head{nullptr};
ABSL_CONST_INIT std::vector<VModuleInfo>* vmodule_info ABSL_GUARDED_BY(mutex)
ABSL_PT_GUARDED_BY(mutex){nullptr};
// Only used for lisp.
ABSL_CONST_INIT std::vector<std::function<void()>>* update_callbacks
ABSL_GUARDED_BY(GetUpdateSitesMutex())
ABSL_PT_GUARDED_BY(GetUpdateSitesMutex()){nullptr};
// Allocates memory.
std::vector<VModuleInfo>& get_vmodule_info()
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) {
if (!vmodule_info) vmodule_info = new std::vector<VModuleInfo>;
return *vmodule_info;
}
// Does not allocate or take locks.
int VLogLevel(absl::string_view file, const std::vector<VModuleInfo>* infos,
int current_global_v) {
// `infos` is null during a call to `VLOG` prior to setting `vmodule` (e.g. by
// parsing flags). We can't allocate in `VLOG`, so we treat null as empty
// here and press on.
if (!infos || infos->empty()) return current_global_v;
// Get basename for file
absl::string_view basename = file;
{
const size_t sep = basename.rfind('/');
if (sep != basename.npos) {
basename.remove_prefix(sep + 1);
#ifdef _WIN32
} else {
const size_t sep = basename.rfind('\\');
if (sep != basename.npos) basename.remove_prefix(sep + 1);
#endif
}
}
absl::string_view stem = file, stem_basename = basename;
{
const size_t sep = stem_basename.find('.');
if (sep != stem_basename.npos) {
stem.remove_suffix(stem_basename.size() - sep);
stem_basename.remove_suffix(stem_basename.size() - sep);
}
if (absl::ConsumeSuffix(&stem_basename, "-inl")) {
stem.remove_suffix(absl::string_view("-inl").size());
}
}
for (const auto& info : *infos) {
if (info.module_is_path) {
// If there are any slashes in the pattern, try to match the full
// name.
if (FNMatch(info.module_pattern, stem)) {
return info.vlog_level == kUseFlag ? current_global_v : info.vlog_level;
}
} else if (FNMatch(info.module_pattern, stem_basename)) {
return info.vlog_level == kUseFlag ? current_global_v : info.vlog_level;
}
}
return current_global_v;
}
// Allocates memory.
int AppendVModuleLocked(absl::string_view module_pattern, int log_level)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) {
for (const auto& info : get_vmodule_info()) {
if (FNMatch(info.module_pattern, module_pattern)) {
// This is a memory optimization to avoid storing patterns that will never
// match due to exit early semantics. Primarily optimized for our own unit
// tests.
return info.vlog_level;
}
}
bool module_is_path = ModuleIsPath(module_pattern);
get_vmodule_info().emplace_back(std::string(module_pattern), module_is_path,
log_level);
return global_v;
}
// Allocates memory.
int PrependVModuleLocked(absl::string_view module_pattern, int log_level)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) {
absl::optional<int> old_log_level;
for (const auto& info : get_vmodule_info()) {
if (FNMatch(info.module_pattern, module_pattern)) {
old_log_level = info.vlog_level;
break;
}
}
bool module_is_path = ModuleIsPath(module_pattern);
auto iter = get_vmodule_info().emplace(get_vmodule_info().cbegin(),
std::string(module_pattern),
module_is_path, log_level);
// This is a memory optimization to avoid storing patterns that will never
// match due to exit early semantics. Primarily optimized for our own unit
// tests.
get_vmodule_info().erase(
std::remove_if(++iter, get_vmodule_info().end(),
[module_pattern](const VModuleInfo& info) {
// Remove the previous pattern if it is less generic than
// the new one. For example, if the new pattern
// `module_pattern` is "foo*" and the previous pattern
// `info.module_pattern` is "foo", we should remove the
// previous pattern. Because the new pattern "foo*" will
// match all the files that the previous pattern "foo"
// matches.
return FNMatch(module_pattern, info.module_pattern);
}),
get_vmodule_info().cend());
return old_log_level.value_or(global_v);
}
} // namespace
int VLogLevel(absl::string_view file) ABSL_LOCKS_EXCLUDED(mutex) {
absl::base_internal::SpinLockHolder l(&mutex);
return VLogLevel(file, vmodule_info, global_v);
}
int RegisterAndInitialize(VLogSite* v) ABSL_LOCKS_EXCLUDED(mutex) {
// std::memory_order_seq_cst is overkill in this function, but given that this
// path is intended to be slow, it's not worth the brain power to relax that.
VLogSite* h = site_list_head.load(std::memory_order_seq_cst);
VLogSite* old = nullptr;
if (v->next_.compare_exchange_strong(old, h, std::memory_order_seq_cst,
std::memory_order_seq_cst)) {
// Multiple threads may attempt to register this site concurrently.
// By successfully setting `v->next` this thread commits to being *the*
// thread that installs `v` in the list.
while (!site_list_head.compare_exchange_weak(
h, v, std::memory_order_seq_cst, std::memory_order_seq_cst)) {
v->next_.store(h, std::memory_order_seq_cst);
}
}
int old_v = VLogSite::kUninitialized;
int new_v = VLogLevel(v->file_);
// No loop, if someone else set this, we should respect their evaluation of
// `VLogLevel`. This may mean we return a stale `v`, but `v` itself will
// always arrive at the freshest value. Otherwise, we could be writing a
// stale value and clobbering the fresher one.
if (v->v_.compare_exchange_strong(old_v, new_v, std::memory_order_seq_cst,
std::memory_order_seq_cst)) {
return new_v;
}
return old_v;
}
void UpdateVLogSites() ABSL_UNLOCK_FUNCTION(mutex)
ABSL_LOCKS_EXCLUDED(GetUpdateSitesMutex()) {
std::vector<VModuleInfo> infos = get_vmodule_info();
int current_global_v = global_v;
// We need to grab `GetUpdateSitesMutex()` before we release `mutex` to ensure
// that updates are not interleaved (resulting in an inconsistent final state)
// and to ensure that the final state in the sites matches the final state of
// `vmodule_info`. We unlock `mutex` to ensure that uninitialized sites don't
// have to wait on all updates in order to acquire `mutex` and initialize
// themselves.
absl::MutexLock ul(GetUpdateSitesMutex());
mutex.Unlock();
VLogSite* n = site_list_head.load(std::memory_order_seq_cst);
// Because sites are added to the list in the order they are executed, there
// tend to be clusters of entries with the same file.
const char* last_file = nullptr;
int last_file_level = 0;
while (n != nullptr) {
if (n->file_ != last_file) {
last_file = n->file_;
last_file_level = VLogLevel(n->file_, &infos, current_global_v);
}
n->v_.store(last_file_level, std::memory_order_seq_cst);
n = n->next_.load(std::memory_order_seq_cst);
}
if (update_callbacks) {
for (auto& cb : *update_callbacks) {
cb();
}
}
}
void UpdateVModule(absl::string_view vmodule)
ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) {
std::vector<std::pair<absl::string_view, int>> glob_levels;
for (absl::string_view glob_level : absl::StrSplit(vmodule, ',')) {
const size_t eq = glob_level.rfind('=');
if (eq == glob_level.npos) continue;
const absl::string_view glob = glob_level.substr(0, eq);
int level;
if (!absl::SimpleAtoi(glob_level.substr(eq + 1), &level)) continue;
glob_levels.emplace_back(glob, level);
}
mutex.Lock(); // Unlocked by UpdateVLogSites().
get_vmodule_info().clear();
for (const auto& it : glob_levels) {
const absl::string_view glob = it.first;
const int level = it.second;
AppendVModuleLocked(glob, level);
}
UpdateVLogSites();
}
int UpdateGlobalVLogLevel(int v)
ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) {
mutex.Lock(); // Unlocked by UpdateVLogSites().
const int old_global_v = global_v;
if (v == global_v) {
mutex.Unlock();
return old_global_v;
}
global_v = v;
UpdateVLogSites();
return old_global_v;
}
int PrependVModule(absl::string_view module_pattern, int log_level)
ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) {
mutex.Lock(); // Unlocked by UpdateVLogSites().
int old_v = PrependVModuleLocked(module_pattern, log_level);
UpdateVLogSites();
return old_v;
}
void OnVLogVerbosityUpdate(std::function<void()> cb)
ABSL_LOCKS_EXCLUDED(GetUpdateSitesMutex()) {
absl::MutexLock ul(GetUpdateSitesMutex());
if (!update_callbacks)
update_callbacks = new std::vector<std::function<void()>>;
update_callbacks->push_back(std::move(cb));
}
VLogSite* SetVModuleListHeadForTestOnly(VLogSite* v) {
return site_list_head.exchange(v, std::memory_order_seq_cst);
}
} // namespace log_internal
ABSL_NAMESPACE_END
} // namespace absl