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