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