• 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_ring.h"
23 #include "absl/strings/internal/cordz_handle.h"
24 #include "absl/strings/internal/cordz_statistics.h"
25 #include "absl/strings/internal/cordz_update_tracker.h"
26 #include "absl/synchronization/mutex.h"
27 #include "absl/types/span.h"
28 
29 namespace absl {
30 ABSL_NAMESPACE_BEGIN
31 namespace cord_internal {
32 
33 using ::absl::base_internal::SpinLockHolder;
34 
35 constexpr int CordzInfo::kMaxStackDepth;
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 // substract 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`.
CordRepAnalyzer(CordzStatistics & 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.
AnalyzeCordRep(const CordRep * rep)76   void AnalyzeCordRep(const CordRep* rep) {
77     // Process all linear nodes.
78     // As per the class comments, use refcout - 1 on the top level node, as the
79     // top level node is assumed to be referenced only for analysis purposes.
80     size_t refcount = rep->refcount.Get();
81     RepRef repref{rep, (refcount > 1) ? refcount - 1 : 1};
82 
83     // Process all top level linear nodes (substrings and flats).
84     repref = CountLinearReps(repref, memory_usage_);
85 
86     // We should have have either a concat or ring node node if not null.
87     if (repref.rep != nullptr) {
88       assert(repref.rep->tag == RING || repref.rep->tag == CONCAT);
89       if (repref.rep->tag == RING) {
90         AnalyzeRing(repref);
91       } else if (repref.rep->tag == CONCAT) {
92         AnalyzeConcat(repref);
93       }
94     }
95 
96     // Adds values to output
97     statistics_.estimated_memory_usage += memory_usage_.total;
98     statistics_.estimated_fair_share_memory_usage += memory_usage_.fair_share;
99   }
100 
101  private:
102   // RepRef identifies a CordRep* inside the Cord tree with its cumulative
103   // refcount including itself. For example, a tree consisting of a substring
104   // with a refcount of 3 and a child flat with a refcount of 4 will have RepRef
105   // refcounts of 3 and 12 respectively.
106   struct RepRef {
107     const CordRep* rep;
108     size_t refcount;
109 
110     // Returns a 'child' RepRef which contains the cumulative reference count of
111     // this instance multiplied by the child's reference count.
Childabsl::cord_internal::__anon2059ca8d0111::CordRepAnalyzer::RepRef112     RepRef Child(const CordRep* child) const {
113       return RepRef{child, refcount * child->refcount.Get()};
114     }
115   };
116 
117   // Memory usage values
118   struct MemoryUsage {
119     size_t total = 0;
120     size_t fair_share = 0;
121 
122     // Adds 'size` memory usage to this class, with a cumulative (recursive)
123     // reference count of `refcount`
Addabsl::cord_internal::__anon2059ca8d0111::CordRepAnalyzer::MemoryUsage124     void Add(size_t size, size_t refcount) {
125       total += size;
126       fair_share += size / refcount;
127     }
128   };
129 
130   // Returns `rr` if `rr.rep` is not null and a CONCAT type.
131   // Asserts that `rr.rep` is a concat node or null.
AssertConcat(RepRef repref)132   static RepRef AssertConcat(RepRef repref) {
133     const CordRep* rep = repref.rep;
134     assert(rep == nullptr || rep->tag == CONCAT);
135     return (rep != nullptr && rep->tag == CONCAT) ? repref : RepRef{nullptr, 0};
136   }
137 
138   // Counts a flat of the provide allocated size
CountFlat(size_t size)139   void CountFlat(size_t size) {
140     statistics_.node_count++;
141     statistics_.node_counts.flat++;
142     if (size <= 64) {
143       statistics_.node_counts.flat_64++;
144     } else if (size <= 128) {
145       statistics_.node_counts.flat_128++;
146     } else if (size <= 256) {
147       statistics_.node_counts.flat_256++;
148     } else if (size <= 512) {
149       statistics_.node_counts.flat_512++;
150     } else if (size <= 1024) {
151       statistics_.node_counts.flat_1k++;
152     }
153   }
154 
155   // Processes 'linear' reps (substring, flat, external) not requiring iteration
156   // or recursion. Returns RefRep{null} if all reps were processed, else returns
157   // the top-most non-linear concat or ring cordrep.
158   // Node counts are updated into `statistics_`, memory usage is update into
159   // `memory_usage`, which typically references `memory_usage_` except for ring
160   // buffers where we count children unrounded.
CountLinearReps(RepRef rep,MemoryUsage & memory_usage)161   RepRef CountLinearReps(RepRef rep, MemoryUsage& memory_usage) {
162     // Consume all substrings
163     while (rep.rep->tag == SUBSTRING) {
164       statistics_.node_count++;
165       statistics_.node_counts.substring++;
166       memory_usage.Add(sizeof(CordRepSubstring), rep.refcount);
167       rep = rep.Child(rep.rep->substring()->child);
168     }
169 
170     // Consume possible FLAT
171     if (rep.rep->tag >= FLAT) {
172       size_t size = rep.rep->flat()->AllocatedSize();
173       CountFlat(size);
174       memory_usage.Add(size, rep.refcount);
175       return RepRef{nullptr, 0};
176     }
177 
178     // Consume possible external
179     if (rep.rep->tag == EXTERNAL) {
180       statistics_.node_count++;
181       statistics_.node_counts.external++;
182       size_t size = rep.rep->length + sizeof(CordRepExternalImpl<intptr_t>);
183       memory_usage.Add(size, rep.refcount);
184       return RepRef{nullptr, 0};
185     }
186 
187     return rep;
188   }
189 
190   // Analyzes the provided concat node in a flattened recursive way.
AnalyzeConcat(RepRef rep)191   void AnalyzeConcat(RepRef rep) {
192     absl::InlinedVector<RepRef, 47> pending;
193 
194     while (rep.rep != nullptr) {
195       const CordRepConcat* concat = rep.rep->concat();
196       RepRef left = rep.Child(concat->left);
197       RepRef right = rep.Child(concat->right);
198 
199       statistics_.node_count++;
200       statistics_.node_counts.concat++;
201       memory_usage_.Add(sizeof(CordRepConcat), rep.refcount);
202 
203       right = AssertConcat(CountLinearReps(right, memory_usage_));
204       rep = AssertConcat(CountLinearReps(left, memory_usage_));
205       if (rep.rep != nullptr) {
206         if (right.rep != nullptr) {
207           pending.push_back(right);
208         }
209       } else if (right.rep != nullptr) {
210         rep = right;
211       } else if (!pending.empty()) {
212         rep = pending.back();
213         pending.pop_back();
214       }
215     }
216   }
217 
218   // Counts the provided ring buffer child into `child_usage`.
CountRingChild(const CordRep * child,MemoryUsage & child_usage)219   void CountRingChild(const CordRep* child, MemoryUsage& child_usage) {
220     RepRef rep{child, static_cast<size_t>(child->refcount.Get())};
221     rep = CountLinearReps(rep, child_usage);
222     assert(rep.rep == nullptr);
223   }
224 
225   // Analyzes the provided ring. As ring buffers can have many child nodes, the
226   // effect of rounding errors can become non trivial, so we compute the totals
227   // first at the ring level, and then divide the fair share of the total
228   // including children fair share totals.
AnalyzeRing(RepRef rep)229   void AnalyzeRing(RepRef rep) {
230     statistics_.node_count++;
231     statistics_.node_counts.ring++;
232     MemoryUsage ring_usage;
233     const CordRepRing* ring = rep.rep->ring();
234     ring_usage.Add(CordRepRing::AllocSize(ring->capacity()), 1);
235     ring->ForEach([&](CordRepRing::index_type pos) {
236       CountRingChild(ring->entry_child(pos), ring_usage);
237     });
238     memory_usage_.total += ring_usage.total;
239     memory_usage_.fair_share += ring_usage.fair_share / rep.refcount;
240   }
241 
242   CordzStatistics& statistics_;
243   MemoryUsage memory_usage_;
244 };
245 
246 }  // namespace
247 
Head(const CordzSnapshot & snapshot)248 CordzInfo* CordzInfo::Head(const CordzSnapshot& snapshot) {
249   ABSL_ASSERT(snapshot.is_snapshot());
250 
251   // We can do an 'unsafe' load of 'head', as we are guaranteed that the
252   // instance it points to is kept alive by the provided CordzSnapshot, so we
253   // can simply return the current value using an acquire load.
254   // We do enforce in DEBUG builds that the 'head' value is present in the
255   // delete queue: ODR violations may lead to 'snapshot' and 'global_list_'
256   // being in different libraries / modules.
257   CordzInfo* head = global_list_.head.load(std::memory_order_acquire);
258   ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(head));
259   return head;
260 }
261 
Next(const CordzSnapshot & snapshot) const262 CordzInfo* CordzInfo::Next(const CordzSnapshot& snapshot) const {
263   ABSL_ASSERT(snapshot.is_snapshot());
264 
265   // Similar to the 'Head()' function, we do not need a mutex here.
266   CordzInfo* next = ci_next_.load(std::memory_order_acquire);
267   ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(this));
268   ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(next));
269   return next;
270 }
271 
TrackCord(InlineData & cord,MethodIdentifier method)272 void CordzInfo::TrackCord(InlineData& cord, MethodIdentifier method) {
273   assert(cord.is_tree());
274   assert(!cord.is_profiled());
275   CordzInfo* cordz_info = new CordzInfo(cord.as_tree(), nullptr, method);
276   cord.set_cordz_info(cordz_info);
277   cordz_info->Track();
278 }
279 
TrackCord(InlineData & cord,const InlineData & src,MethodIdentifier method)280 void CordzInfo::TrackCord(InlineData& cord, const InlineData& src,
281                           MethodIdentifier method) {
282   assert(cord.is_tree());
283   assert(src.is_tree());
284 
285   // Unsample current as we the current cord is being replaced with 'src',
286   // so any method history is no longer relevant.
287   CordzInfo* cordz_info = cord.cordz_info();
288   if (cordz_info != nullptr) cordz_info->Untrack();
289 
290   // Start new cord sample
291   cordz_info = new CordzInfo(cord.as_tree(), src.cordz_info(), method);
292   cord.set_cordz_info(cordz_info);
293   cordz_info->Track();
294 }
295 
MaybeTrackCordImpl(InlineData & cord,const InlineData & src,MethodIdentifier method)296 void CordzInfo::MaybeTrackCordImpl(InlineData& cord, const InlineData& src,
297                                    MethodIdentifier method) {
298   if (src.is_profiled()) {
299     TrackCord(cord, src, method);
300   } else if (cord.is_profiled()) {
301     cord.cordz_info()->Untrack();
302     cord.clear_cordz_info();
303   }
304 }
305 
GetParentMethod(const CordzInfo * src)306 CordzInfo::MethodIdentifier CordzInfo::GetParentMethod(const CordzInfo* src) {
307   if (src == nullptr) return MethodIdentifier::kUnknown;
308   return src->parent_method_ != MethodIdentifier::kUnknown ? src->parent_method_
309                                                            : src->method_;
310 }
311 
FillParentStack(const CordzInfo * src,void ** stack)312 int CordzInfo::FillParentStack(const CordzInfo* src, void** stack) {
313   assert(stack);
314   if (src == nullptr) return 0;
315   if (src->parent_stack_depth_) {
316     memcpy(stack, src->parent_stack_, src->parent_stack_depth_ * sizeof(void*));
317     return src->parent_stack_depth_;
318   }
319   memcpy(stack, src->stack_, src->stack_depth_ * sizeof(void*));
320   return src->stack_depth_;
321 }
322 
CordzInfo(CordRep * rep,const CordzInfo * src,MethodIdentifier method)323 CordzInfo::CordzInfo(CordRep* rep, const CordzInfo* src,
324                      MethodIdentifier method)
325     : rep_(rep),
326       stack_depth_(absl::GetStackTrace(stack_, /*max_depth=*/kMaxStackDepth,
327                                        /*skip_count=*/1)),
328       parent_stack_depth_(FillParentStack(src, parent_stack_)),
329       method_(method),
330       parent_method_(GetParentMethod(src)),
331       create_time_(absl::Now()) {
332   update_tracker_.LossyAdd(method);
333   if (src) {
334     // Copy parent counters.
335     update_tracker_.LossyAdd(src->update_tracker_);
336   }
337 }
338 
~CordzInfo()339 CordzInfo::~CordzInfo() {
340   // `rep_` is potentially kept alive if CordzInfo is included
341   // in a collection snapshot (which should be rare).
342   if (ABSL_PREDICT_FALSE(rep_)) {
343     CordRep::Unref(rep_);
344   }
345 }
346 
Track()347 void CordzInfo::Track() {
348   SpinLockHolder l(&list_->mutex);
349 
350   CordzInfo* const head = list_->head.load(std::memory_order_acquire);
351   if (head != nullptr) {
352     head->ci_prev_.store(this, std::memory_order_release);
353   }
354   ci_next_.store(head, std::memory_order_release);
355   list_->head.store(this, std::memory_order_release);
356 }
357 
Untrack()358 void CordzInfo::Untrack() {
359   ODRCheck();
360   {
361     SpinLockHolder l(&list_->mutex);
362 
363     CordzInfo* const head = list_->head.load(std::memory_order_acquire);
364     CordzInfo* const next = ci_next_.load(std::memory_order_acquire);
365     CordzInfo* const prev = ci_prev_.load(std::memory_order_acquire);
366 
367     if (next) {
368       ABSL_ASSERT(next->ci_prev_.load(std::memory_order_acquire) == this);
369       next->ci_prev_.store(prev, std::memory_order_release);
370     }
371     if (prev) {
372       ABSL_ASSERT(head != this);
373       ABSL_ASSERT(prev->ci_next_.load(std::memory_order_acquire) == this);
374       prev->ci_next_.store(next, std::memory_order_release);
375     } else {
376       ABSL_ASSERT(head == this);
377       list_->head.store(next, std::memory_order_release);
378     }
379   }
380 
381   // We can no longer be discovered: perform a fast path check if we are not
382   // listed on any delete queue, so we can directly delete this instance.
383   if (SafeToDelete()) {
384     UnsafeSetCordRep(nullptr);
385     delete this;
386     return;
387   }
388 
389   // We are likely part of a snapshot, extend the life of the CordRep
390   {
391     absl::MutexLock lock(&mutex_);
392     if (rep_) CordRep::Ref(rep_);
393   }
394   CordzHandle::Delete(this);
395 }
396 
Lock(MethodIdentifier method)397 void CordzInfo::Lock(MethodIdentifier method)
398     ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex_) {
399   mutex_.Lock();
400   update_tracker_.LossyAdd(method);
401   assert(rep_);
402 }
403 
Unlock()404 void CordzInfo::Unlock() ABSL_UNLOCK_FUNCTION(mutex_) {
405   bool tracked = rep_ != nullptr;
406   mutex_.Unlock();
407   if (!tracked) {
408     Untrack();
409   }
410 }
411 
GetStack() const412 absl::Span<void* const> CordzInfo::GetStack() const {
413   return absl::MakeConstSpan(stack_, stack_depth_);
414 }
415 
GetParentStack() const416 absl::Span<void* const> CordzInfo::GetParentStack() const {
417   return absl::MakeConstSpan(parent_stack_, parent_stack_depth_);
418 }
419 
GetCordzStatistics() const420 CordzStatistics CordzInfo::GetCordzStatistics() const {
421   CordzStatistics stats;
422   stats.method = method_;
423   stats.parent_method = parent_method_;
424   stats.update_tracker = update_tracker_;
425   if (CordRep* rep = RefCordRep()) {
426     stats.size = rep->length;
427     CordRepAnalyzer analyzer(stats);
428     analyzer.AnalyzeCordRep(rep);
429     CordRep::Unref(rep);
430   }
431   return stats;
432 }
433 
434 }  // namespace cord_internal
435 ABSL_NAMESPACE_END
436 }  // namespace absl
437