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