vlog_config.cc (13171B)
1 // Copyright 2022 The Abseil Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "absl/log/internal/vlog_config.h" 16 17 #include <stddef.h> 18 19 #include <algorithm> 20 #include <atomic> 21 #include <functional> 22 #include <memory> 23 #include <string> 24 #include <utility> 25 #include <vector> 26 27 #include "absl/base/attributes.h" 28 #include "absl/base/config.h" 29 #include "absl/base/const_init.h" 30 #include "absl/base/internal/spinlock.h" 31 #include "absl/base/no_destructor.h" 32 #include "absl/base/optimization.h" 33 #include "absl/base/thread_annotations.h" 34 #include "absl/log/internal/fnmatch.h" 35 #include "absl/memory/memory.h" 36 #include "absl/strings/numbers.h" 37 #include "absl/strings/str_split.h" 38 #include "absl/strings/string_view.h" 39 #include "absl/strings/strip.h" 40 #include "absl/synchronization/mutex.h" 41 #include "absl/types/optional.h" 42 43 namespace absl { 44 ABSL_NAMESPACE_BEGIN 45 namespace log_internal { 46 47 namespace { 48 bool ModuleIsPath(absl::string_view module_pattern) { 49 #ifdef _WIN32 50 return module_pattern.find_first_of("/\\") != module_pattern.npos; 51 #else 52 return module_pattern.find('/') != module_pattern.npos; 53 #endif 54 } 55 } // namespace 56 57 bool VLogSite::SlowIsEnabled(int stale_v, int level) { 58 if (ABSL_PREDICT_TRUE(stale_v != kUninitialized)) { 59 // Because of the prerequisites to this function, we know that stale_v is 60 // either uninitialized or >= level. If it's not uninitialized, that means 61 // it must be >= level, thus we should log. 62 return true; 63 } 64 stale_v = log_internal::RegisterAndInitialize(this); 65 return ABSL_PREDICT_FALSE(stale_v >= level); 66 } 67 68 bool VLogSite::SlowIsEnabled0(int stale_v) { return SlowIsEnabled(stale_v, 0); } 69 bool VLogSite::SlowIsEnabled1(int stale_v) { return SlowIsEnabled(stale_v, 1); } 70 bool VLogSite::SlowIsEnabled2(int stale_v) { return SlowIsEnabled(stale_v, 2); } 71 bool VLogSite::SlowIsEnabled3(int stale_v) { return SlowIsEnabled(stale_v, 3); } 72 bool VLogSite::SlowIsEnabled4(int stale_v) { return SlowIsEnabled(stale_v, 4); } 73 bool VLogSite::SlowIsEnabled5(int stale_v) { return SlowIsEnabled(stale_v, 5); } 74 75 namespace { 76 struct VModuleInfo final { 77 std::string module_pattern; 78 bool module_is_path; // i.e. it contains a path separator. 79 int vlog_level; 80 81 // Allocates memory. 82 VModuleInfo(absl::string_view module_pattern_arg, bool module_is_path_arg, 83 int vlog_level_arg) 84 : module_pattern(std::string(module_pattern_arg)), 85 module_is_path(module_is_path_arg), 86 vlog_level(vlog_level_arg) {} 87 }; 88 89 // `mutex` guards all of the data structures that aren't lock-free. 90 // To avoid problems with the heap checker which calls into `VLOG`, `mutex` must 91 // be a `SpinLock` that prevents fiber scheduling instead of a `Mutex`. 92 ABSL_CONST_INIT absl::base_internal::SpinLock mutex( 93 absl::kConstInit, absl::base_internal::SCHEDULE_KERNEL_ONLY); 94 95 // `GetUpdateSitesMutex()` serializes updates to all of the sites (i.e. those in 96 // `site_list_head`) themselves. 97 absl::Mutex* GetUpdateSitesMutex() { 98 // Chromium requires no global destructors, so we can't use the 99 // absl::kConstInit idiom since absl::Mutex as a non-trivial destructor. 100 static absl::NoDestructor<absl::Mutex> update_sites_mutex ABSL_ACQUIRED_AFTER( 101 mutex); 102 return update_sites_mutex.get(); 103 } 104 105 ABSL_CONST_INIT int global_v ABSL_GUARDED_BY(mutex) = 0; 106 // `site_list_head` is the head of a singly-linked list. Traversal, insertion, 107 // and reads are atomic, so no locks are required, but updates to existing 108 // elements are guarded by `GetUpdateSitesMutex()`. 109 ABSL_CONST_INIT std::atomic<VLogSite*> site_list_head{nullptr}; 110 ABSL_CONST_INIT std::vector<VModuleInfo>* vmodule_info ABSL_GUARDED_BY(mutex) 111 ABSL_PT_GUARDED_BY(mutex){nullptr}; 112 113 // Only used for lisp. 114 ABSL_CONST_INIT std::vector<std::function<void()>>* update_callbacks 115 ABSL_GUARDED_BY(GetUpdateSitesMutex()) 116 ABSL_PT_GUARDED_BY(GetUpdateSitesMutex()){nullptr}; 117 118 // Allocates memory. 119 std::vector<VModuleInfo>& get_vmodule_info() 120 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) { 121 if (!vmodule_info) vmodule_info = new std::vector<VModuleInfo>; 122 return *vmodule_info; 123 } 124 125 // Does not allocate or take locks. 126 int VLogLevel(absl::string_view file, const std::vector<VModuleInfo>* infos, 127 int current_global_v) { 128 // `infos` is null during a call to `VLOG` prior to setting `vmodule` (e.g. by 129 // parsing flags). We can't allocate in `VLOG`, so we treat null as empty 130 // here and press on. 131 if (!infos || infos->empty()) return current_global_v; 132 // Get basename for file 133 absl::string_view basename = file; 134 { 135 const size_t sep = basename.rfind('/'); 136 if (sep != basename.npos) { 137 basename.remove_prefix(sep + 1); 138 #ifdef _WIN32 139 } else { 140 const size_t sep = basename.rfind('\\'); 141 if (sep != basename.npos) basename.remove_prefix(sep + 1); 142 #endif 143 } 144 } 145 146 absl::string_view stem = file, stem_basename = basename; 147 { 148 const size_t sep = stem_basename.find('.'); 149 if (sep != stem_basename.npos) { 150 stem.remove_suffix(stem_basename.size() - sep); 151 stem_basename.remove_suffix(stem_basename.size() - sep); 152 } 153 if (absl::ConsumeSuffix(&stem_basename, "-inl")) { 154 stem.remove_suffix(absl::string_view("-inl").size()); 155 } 156 } 157 for (const auto& info : *infos) { 158 if (info.module_is_path) { 159 // If there are any slashes in the pattern, try to match the full 160 // name. 161 if (FNMatch(info.module_pattern, stem)) { 162 return info.vlog_level == kUseFlag ? current_global_v : info.vlog_level; 163 } 164 } else if (FNMatch(info.module_pattern, stem_basename)) { 165 return info.vlog_level == kUseFlag ? current_global_v : info.vlog_level; 166 } 167 } 168 169 return current_global_v; 170 } 171 172 // Allocates memory. 173 int AppendVModuleLocked(absl::string_view module_pattern, int log_level) 174 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) { 175 for (const auto& info : get_vmodule_info()) { 176 if (FNMatch(info.module_pattern, module_pattern)) { 177 // This is a memory optimization to avoid storing patterns that will never 178 // match due to exit early semantics. Primarily optimized for our own unit 179 // tests. 180 return info.vlog_level; 181 } 182 } 183 bool module_is_path = ModuleIsPath(module_pattern); 184 get_vmodule_info().emplace_back(std::string(module_pattern), module_is_path, 185 log_level); 186 return global_v; 187 } 188 189 // Allocates memory. 190 int PrependVModuleLocked(absl::string_view module_pattern, int log_level) 191 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) { 192 absl::optional<int> old_log_level; 193 for (const auto& info : get_vmodule_info()) { 194 if (FNMatch(info.module_pattern, module_pattern)) { 195 old_log_level = info.vlog_level; 196 break; 197 } 198 } 199 bool module_is_path = ModuleIsPath(module_pattern); 200 auto iter = get_vmodule_info().emplace(get_vmodule_info().cbegin(), 201 std::string(module_pattern), 202 module_is_path, log_level); 203 204 // This is a memory optimization to avoid storing patterns that will never 205 // match due to exit early semantics. Primarily optimized for our own unit 206 // tests. 207 get_vmodule_info().erase( 208 std::remove_if(++iter, get_vmodule_info().end(), 209 [module_pattern](const VModuleInfo& info) { 210 // Remove the previous pattern if it is less generic than 211 // the new one. For example, if the new pattern 212 // `module_pattern` is "foo*" and the previous pattern 213 // `info.module_pattern` is "foo", we should remove the 214 // previous pattern. Because the new pattern "foo*" will 215 // match all the files that the previous pattern "foo" 216 // matches. 217 return FNMatch(module_pattern, info.module_pattern); 218 }), 219 get_vmodule_info().cend()); 220 return old_log_level.value_or(global_v); 221 } 222 } // namespace 223 224 int VLogLevel(absl::string_view file) ABSL_LOCKS_EXCLUDED(mutex) { 225 absl::base_internal::SpinLockHolder l(&mutex); 226 return VLogLevel(file, vmodule_info, global_v); 227 } 228 229 int RegisterAndInitialize(VLogSite* v) ABSL_LOCKS_EXCLUDED(mutex) { 230 // std::memory_order_seq_cst is overkill in this function, but given that this 231 // path is intended to be slow, it's not worth the brain power to relax that. 232 VLogSite* h = site_list_head.load(std::memory_order_seq_cst); 233 234 VLogSite* old = nullptr; 235 if (v->next_.compare_exchange_strong(old, h, std::memory_order_seq_cst, 236 std::memory_order_seq_cst)) { 237 // Multiple threads may attempt to register this site concurrently. 238 // By successfully setting `v->next` this thread commits to being *the* 239 // thread that installs `v` in the list. 240 while (!site_list_head.compare_exchange_weak( 241 h, v, std::memory_order_seq_cst, std::memory_order_seq_cst)) { 242 v->next_.store(h, std::memory_order_seq_cst); 243 } 244 } 245 246 int old_v = VLogSite::kUninitialized; 247 int new_v = VLogLevel(v->file_); 248 // No loop, if someone else set this, we should respect their evaluation of 249 // `VLogLevel`. This may mean we return a stale `v`, but `v` itself will 250 // always arrive at the freshest value. Otherwise, we could be writing a 251 // stale value and clobbering the fresher one. 252 if (v->v_.compare_exchange_strong(old_v, new_v, std::memory_order_seq_cst, 253 std::memory_order_seq_cst)) { 254 return new_v; 255 } 256 return old_v; 257 } 258 259 void UpdateVLogSites() ABSL_UNLOCK_FUNCTION(mutex) 260 ABSL_LOCKS_EXCLUDED(GetUpdateSitesMutex()) { 261 std::vector<VModuleInfo> infos = get_vmodule_info(); 262 int current_global_v = global_v; 263 // We need to grab `GetUpdateSitesMutex()` before we release `mutex` to ensure 264 // that updates are not interleaved (resulting in an inconsistent final state) 265 // and to ensure that the final state in the sites matches the final state of 266 // `vmodule_info`. We unlock `mutex` to ensure that uninitialized sites don't 267 // have to wait on all updates in order to acquire `mutex` and initialize 268 // themselves. 269 absl::MutexLock ul(GetUpdateSitesMutex()); 270 mutex.Unlock(); 271 VLogSite* n = site_list_head.load(std::memory_order_seq_cst); 272 // Because sites are added to the list in the order they are executed, there 273 // tend to be clusters of entries with the same file. 274 const char* last_file = nullptr; 275 int last_file_level = 0; 276 while (n != nullptr) { 277 if (n->file_ != last_file) { 278 last_file = n->file_; 279 last_file_level = VLogLevel(n->file_, &infos, current_global_v); 280 } 281 n->v_.store(last_file_level, std::memory_order_seq_cst); 282 n = n->next_.load(std::memory_order_seq_cst); 283 } 284 if (update_callbacks) { 285 for (auto& cb : *update_callbacks) { 286 cb(); 287 } 288 } 289 } 290 291 void UpdateVModule(absl::string_view vmodule) 292 ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) { 293 std::vector<std::pair<absl::string_view, int>> glob_levels; 294 for (absl::string_view glob_level : absl::StrSplit(vmodule, ',')) { 295 const size_t eq = glob_level.rfind('='); 296 if (eq == glob_level.npos) continue; 297 const absl::string_view glob = glob_level.substr(0, eq); 298 int level; 299 if (!absl::SimpleAtoi(glob_level.substr(eq + 1), &level)) continue; 300 glob_levels.emplace_back(glob, level); 301 } 302 mutex.Lock(); // Unlocked by UpdateVLogSites(). 303 get_vmodule_info().clear(); 304 for (const auto& it : glob_levels) { 305 const absl::string_view glob = it.first; 306 const int level = it.second; 307 AppendVModuleLocked(glob, level); 308 } 309 UpdateVLogSites(); 310 } 311 312 int UpdateGlobalVLogLevel(int v) 313 ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) { 314 mutex.Lock(); // Unlocked by UpdateVLogSites(). 315 const int old_global_v = global_v; 316 if (v == global_v) { 317 mutex.Unlock(); 318 return old_global_v; 319 } 320 global_v = v; 321 UpdateVLogSites(); 322 return old_global_v; 323 } 324 325 int PrependVModule(absl::string_view module_pattern, int log_level) 326 ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) { 327 mutex.Lock(); // Unlocked by UpdateVLogSites(). 328 int old_v = PrependVModuleLocked(module_pattern, log_level); 329 UpdateVLogSites(); 330 return old_v; 331 } 332 333 void OnVLogVerbosityUpdate(std::function<void()> cb) 334 ABSL_LOCKS_EXCLUDED(GetUpdateSitesMutex()) { 335 absl::MutexLock ul(GetUpdateSitesMutex()); 336 if (!update_callbacks) 337 update_callbacks = new std::vector<std::function<void()>>; 338 update_callbacks->push_back(std::move(cb)); 339 } 340 341 VLogSite* SetVModuleListHeadForTestOnly(VLogSite* v) { 342 return site_list_head.exchange(v, std::memory_order_seq_cst); 343 } 344 345 } // namespace log_internal 346 ABSL_NAMESPACE_END 347 } // namespace absl