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