cordz_info.cc 15 KB

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