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