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::__anon8a7a085d0111::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::__anon8a7a085d0111::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