cordz_info.cc (14763B)
1 // Copyright 2019 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/strings/internal/cordz_info.h" 16 17 #include <cstdint> 18 19 #include "absl/base/config.h" 20 #include "absl/base/internal/spinlock.h" 21 #include "absl/container/inlined_vector.h" 22 #include "absl/debugging/stacktrace.h" 23 #include "absl/strings/internal/cord_internal.h" 24 #include "absl/strings/internal/cord_rep_btree.h" 25 #include "absl/strings/internal/cord_rep_crc.h" 26 #include "absl/strings/internal/cordz_handle.h" 27 #include "absl/strings/internal/cordz_statistics.h" 28 #include "absl/strings/internal/cordz_update_tracker.h" 29 #include "absl/synchronization/mutex.h" 30 #include "absl/time/clock.h" 31 #include "absl/types/span.h" 32 33 namespace absl { 34 ABSL_NAMESPACE_BEGIN 35 namespace cord_internal { 36 37 ABSL_CONST_INIT CordzInfo::List CordzInfo::global_list_{absl::kConstInit}; 38 39 namespace { 40 41 // CordRepAnalyzer performs the analysis of a cord. 42 // 43 // It computes absolute node counts and total memory usage, and an 'estimated 44 // fair share memory usage` statistic. 45 // Conceptually, it divides the 'memory usage' at each location in the 'cord 46 // graph' by the cumulative reference count of that location. The cumulative 47 // reference count is the factored total of all edges leading into that node. 48 // 49 // The top level node is treated specially: we assume the current thread 50 // (typically called from the CordzHandler) to hold a reference purely to 51 // perform a safe analysis, and not being part of the application. So we 52 // subtract 1 from the reference count of the top node to compute the 53 // 'application fair share' excluding the reference of the current thread. 54 // 55 // An example of fair sharing, and why we multiply reference counts: 56 // Assume we have 2 CordReps, both being a Substring referencing a Flat: 57 // CordSubstring A (refcount = 5) --> child Flat C (refcount = 2) 58 // CordSubstring B (refcount = 9) --> child Flat C (refcount = 2) 59 // 60 // Flat C has 2 incoming edges from the 2 substrings (refcount = 2) and is not 61 // referenced directly anywhere else. Translated into a 'fair share', we then 62 // attribute 50% of the memory (memory / refcount = 2) to each incoming edge. 63 // Rep A has a refcount of 5, so we attribute each incoming edge 1 / 5th of the 64 // memory cost below it, i.e.: the fair share of Rep A of the memory used by C 65 // is then 'memory C / (refcount C * refcount A) + (memory A / refcount A)'. 66 // It is also easy to see how all incoming edges add up to 100%. 67 class CordRepAnalyzer { 68 public: 69 // Creates an analyzer instance binding to `statistics`. 70 explicit CordRepAnalyzer(CordzStatistics& statistics) 71 : statistics_(statistics) {} 72 73 // Analyzes the memory statistics and node counts for the provided `rep`, and 74 // adds the results to `statistics`. Note that node counts and memory sizes 75 // are not initialized, computed values are added to any existing values. 76 void AnalyzeCordRep(const CordRep* rep) { 77 ABSL_ASSERT(rep != nullptr); 78 79 // Process all linear nodes. 80 // As per the class comments, use refcout - 1 on the top level node, as the 81 // top level node is assumed to be referenced only for analysis purposes. 82 size_t refcount = rep->refcount.Get(); 83 RepRef repref{rep, (refcount > 1) ? refcount - 1 : 1}; 84 85 // Process the top level CRC node, if present. 86 if (repref.tag() == CRC) { 87 statistics_.node_count++; 88 statistics_.node_counts.crc++; 89 memory_usage_.Add(sizeof(CordRepCrc), repref.refcount); 90 repref = repref.Child(repref.rep->crc()->child); 91 } 92 93 // Process all top level linear nodes (substrings and flats). 94 repref = CountLinearReps(repref, memory_usage_); 95 96 switch (repref.tag()) { 97 case CordRepKind::BTREE: 98 AnalyzeBtree(repref); 99 break; 100 default: 101 // We should have a btree node if not null. 102 ABSL_ASSERT(repref.tag() == CordRepKind::UNUSED_0); 103 break; 104 } 105 106 // Adds values to output 107 statistics_.estimated_memory_usage += memory_usage_.total; 108 statistics_.estimated_fair_share_memory_usage += 109 static_cast<size_t>(memory_usage_.fair_share); 110 } 111 112 private: 113 // RepRef identifies a CordRep* inside the Cord tree with its cumulative 114 // refcount including itself. For example, a tree consisting of a substring 115 // with a refcount of 3 and a child flat with a refcount of 4 will have RepRef 116 // refcounts of 3 and 12 respectively. 117 struct RepRef { 118 const CordRep* rep; 119 size_t refcount; 120 121 // Returns a 'child' RepRef which contains the cumulative reference count 122 // of this instance multiplied by the child's reference count. Returns a 123 // nullptr RepRef value with a refcount of 0 if `child` is nullptr. 124 RepRef Child(const CordRep* child) const { 125 if (child == nullptr) return RepRef{nullptr, 0}; 126 return RepRef{child, refcount * child->refcount.Get()}; 127 } 128 129 // Returns the tag of this rep, or UNUSED_0 if this instance is null 130 constexpr CordRepKind tag() const { 131 ABSL_ASSERT(rep == nullptr || rep->tag != CordRepKind::UNUSED_0); 132 return rep ? static_cast<CordRepKind>(rep->tag) : CordRepKind::UNUSED_0; 133 } 134 }; 135 136 // Memory usage values 137 struct MemoryUsage { 138 size_t total = 0; 139 double fair_share = 0.0; 140 141 // Adds 'size` memory usage to this class, with a cumulative (recursive) 142 // reference count of `refcount` 143 void Add(size_t size, size_t refcount) { 144 total += size; 145 fair_share += static_cast<double>(size) / refcount; 146 } 147 }; 148 149 // Counts a flat of the provide allocated size 150 void CountFlat(size_t size) { 151 statistics_.node_count++; 152 statistics_.node_counts.flat++; 153 if (size <= 64) { 154 statistics_.node_counts.flat_64++; 155 } else if (size <= 128) { 156 statistics_.node_counts.flat_128++; 157 } else if (size <= 256) { 158 statistics_.node_counts.flat_256++; 159 } else if (size <= 512) { 160 statistics_.node_counts.flat_512++; 161 } else if (size <= 1024) { 162 statistics_.node_counts.flat_1k++; 163 } 164 } 165 166 // Processes 'linear' reps (substring, flat, external) not requiring iteration 167 // or recursion. Returns RefRep{null} if all reps were processed, else returns 168 // the top-most non-linear concat or ring cordrep. 169 // Node counts are updated into `statistics_`, memory usage is update into 170 // `memory_usage`, which typically references `memory_usage_` except for ring 171 // buffers where we count children unrounded. 172 RepRef CountLinearReps(RepRef rep, MemoryUsage& memory_usage) { 173 // Consume all substrings 174 while (rep.tag() == SUBSTRING) { 175 statistics_.node_count++; 176 statistics_.node_counts.substring++; 177 memory_usage.Add(sizeof(CordRepSubstring), rep.refcount); 178 rep = rep.Child(rep.rep->substring()->child); 179 } 180 181 // Consume possible FLAT 182 if (rep.tag() >= FLAT) { 183 size_t size = rep.rep->flat()->AllocatedSize(); 184 CountFlat(size); 185 memory_usage.Add(size, rep.refcount); 186 return RepRef{nullptr, 0}; 187 } 188 189 // Consume possible external 190 if (rep.tag() == EXTERNAL) { 191 statistics_.node_count++; 192 statistics_.node_counts.external++; 193 size_t size = rep.rep->length + sizeof(CordRepExternalImpl<intptr_t>); 194 memory_usage.Add(size, rep.refcount); 195 return RepRef{nullptr, 0}; 196 } 197 198 return rep; 199 } 200 201 // Analyzes the provided btree. 202 void AnalyzeBtree(RepRef rep) { 203 statistics_.node_count++; 204 statistics_.node_counts.btree++; 205 memory_usage_.Add(sizeof(CordRepBtree), rep.refcount); 206 const CordRepBtree* tree = rep.rep->btree(); 207 if (tree->height() > 0) { 208 for (CordRep* edge : tree->Edges()) { 209 AnalyzeBtree(rep.Child(edge)); 210 } 211 } else { 212 for (CordRep* edge : tree->Edges()) { 213 CountLinearReps(rep.Child(edge), memory_usage_); 214 } 215 } 216 } 217 218 CordzStatistics& statistics_; 219 MemoryUsage memory_usage_; 220 }; 221 222 } // namespace 223 224 CordzInfo* CordzInfo::Head(const CordzSnapshot& snapshot) { 225 ABSL_ASSERT(snapshot.is_snapshot()); 226 227 // We can do an 'unsafe' load of 'head', as we are guaranteed that the 228 // instance it points to is kept alive by the provided CordzSnapshot, so we 229 // can simply return the current value using an acquire load. 230 // We do enforce in DEBUG builds that the 'head' value is present in the 231 // delete queue: ODR violations may lead to 'snapshot' and 'global_list_' 232 // being in different libraries / modules. 233 CordzInfo* head = global_list_.head.load(std::memory_order_acquire); 234 ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(head)); 235 return head; 236 } 237 238 CordzInfo* CordzInfo::Next(const CordzSnapshot& snapshot) const { 239 ABSL_ASSERT(snapshot.is_snapshot()); 240 241 // Similar to the 'Head()' function, we do not need a mutex here. 242 CordzInfo* next = ci_next_.load(std::memory_order_acquire); 243 ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(this)); 244 ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(next)); 245 return next; 246 } 247 248 void CordzInfo::TrackCord(InlineData& cord, MethodIdentifier method, 249 int64_t sampling_stride) { 250 assert(cord.is_tree()); 251 assert(!cord.is_profiled()); 252 CordzInfo* cordz_info = 253 new CordzInfo(cord.as_tree(), nullptr, method, sampling_stride); 254 cord.set_cordz_info(cordz_info); 255 cordz_info->Track(); 256 } 257 258 void CordzInfo::TrackCord(InlineData& cord, const InlineData& src, 259 MethodIdentifier method) { 260 assert(cord.is_tree()); 261 assert(src.is_tree()); 262 263 // Unsample current as we the current cord is being replaced with 'src', 264 // so any method history is no longer relevant. 265 CordzInfo* cordz_info = cord.cordz_info(); 266 if (cordz_info != nullptr) cordz_info->Untrack(); 267 268 // Start new cord sample 269 cordz_info = new CordzInfo(cord.as_tree(), src.cordz_info(), method, 270 src.cordz_info()->sampling_stride()); 271 cord.set_cordz_info(cordz_info); 272 cordz_info->Track(); 273 } 274 275 void CordzInfo::MaybeTrackCordImpl(InlineData& cord, const InlineData& src, 276 MethodIdentifier method) { 277 if (src.is_profiled()) { 278 TrackCord(cord, src, method); 279 } else if (cord.is_profiled()) { 280 cord.cordz_info()->Untrack(); 281 cord.clear_cordz_info(); 282 } 283 } 284 285 CordzInfo::MethodIdentifier CordzInfo::GetParentMethod(const CordzInfo* src) { 286 if (src == nullptr) return MethodIdentifier::kUnknown; 287 return src->parent_method_ != MethodIdentifier::kUnknown ? src->parent_method_ 288 : src->method_; 289 } 290 291 size_t CordzInfo::FillParentStack(const CordzInfo* src, void** stack) { 292 assert(stack); 293 if (src == nullptr) return 0; 294 if (src->parent_stack_depth_) { 295 memcpy(stack, src->parent_stack_, src->parent_stack_depth_ * sizeof(void*)); 296 return src->parent_stack_depth_; 297 } 298 memcpy(stack, src->stack_, src->stack_depth_ * sizeof(void*)); 299 return src->stack_depth_; 300 } 301 302 CordzInfo::CordzInfo(CordRep* rep, const CordzInfo* src, 303 MethodIdentifier method, int64_t sampling_stride) 304 : rep_(rep), 305 stack_depth_( 306 static_cast<size_t>(absl::GetStackTrace(stack_, 307 /*max_depth=*/kMaxStackDepth, 308 /*skip_count=*/1))), 309 parent_stack_depth_(FillParentStack(src, parent_stack_)), 310 method_(method), 311 parent_method_(GetParentMethod(src)), 312 create_time_(absl::Now()), 313 sampling_stride_(sampling_stride) { 314 update_tracker_.LossyAdd(method); 315 if (src) { 316 // Copy parent counters. 317 update_tracker_.LossyAdd(src->update_tracker_); 318 } 319 } 320 321 CordzInfo::~CordzInfo() { 322 // `rep_` is potentially kept alive if CordzInfo is included 323 // in a collection snapshot (which should be rare). 324 if (ABSL_PREDICT_FALSE(rep_)) { 325 CordRep::Unref(rep_); 326 } 327 } 328 329 void CordzInfo::Track() { 330 SpinLockHolder l(&list_->mutex); 331 332 CordzInfo* const head = list_->head.load(std::memory_order_acquire); 333 if (head != nullptr) { 334 head->ci_prev_.store(this, std::memory_order_release); 335 } 336 ci_next_.store(head, std::memory_order_release); 337 list_->head.store(this, std::memory_order_release); 338 } 339 340 void CordzInfo::Untrack() { 341 ODRCheck(); 342 { 343 SpinLockHolder l(&list_->mutex); 344 345 CordzInfo* const head = list_->head.load(std::memory_order_acquire); 346 CordzInfo* const next = ci_next_.load(std::memory_order_acquire); 347 CordzInfo* const prev = ci_prev_.load(std::memory_order_acquire); 348 349 if (next) { 350 ABSL_ASSERT(next->ci_prev_.load(std::memory_order_acquire) == this); 351 next->ci_prev_.store(prev, std::memory_order_release); 352 } 353 if (prev) { 354 ABSL_ASSERT(head != this); 355 ABSL_ASSERT(prev->ci_next_.load(std::memory_order_acquire) == this); 356 prev->ci_next_.store(next, std::memory_order_release); 357 } else { 358 ABSL_ASSERT(head == this); 359 list_->head.store(next, std::memory_order_release); 360 } 361 } 362 363 // We can no longer be discovered: perform a fast path check if we are not 364 // listed on any delete queue, so we can directly delete this instance. 365 if (SafeToDelete()) { 366 UnsafeSetCordRep(nullptr); 367 delete this; 368 return; 369 } 370 371 // We are likely part of a snapshot, extend the life of the CordRep 372 { 373 absl::MutexLock lock(&mutex_); 374 if (rep_) CordRep::Ref(rep_); 375 } 376 CordzHandle::Delete(this); 377 } 378 379 void CordzInfo::Lock(MethodIdentifier method) 380 ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex_) { 381 mutex_.Lock(); 382 update_tracker_.LossyAdd(method); 383 assert(rep_); 384 } 385 386 void CordzInfo::Unlock() ABSL_UNLOCK_FUNCTION(mutex_) { 387 bool tracked = rep_ != nullptr; 388 mutex_.Unlock(); 389 if (!tracked) { 390 Untrack(); 391 } 392 } 393 394 absl::Span<void* const> CordzInfo::GetStack() const { 395 return absl::MakeConstSpan(stack_, stack_depth_); 396 } 397 398 absl::Span<void* const> CordzInfo::GetParentStack() const { 399 return absl::MakeConstSpan(parent_stack_, parent_stack_depth_); 400 } 401 402 CordzStatistics CordzInfo::GetCordzStatistics() const { 403 CordzStatistics stats; 404 stats.method = method_; 405 stats.parent_method = parent_method_; 406 stats.update_tracker = update_tracker_; 407 if (CordRep* rep = RefCordRep()) { 408 stats.size = rep->length; 409 CordRepAnalyzer analyzer(stats); 410 analyzer.AnalyzeCordRep(rep); 411 CordRep::Unref(rep); 412 } 413 return stats; 414 } 415 416 } // namespace cord_internal 417 ABSL_NAMESPACE_END 418 } // namespace absl