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