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