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