tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

cordz_info.cc (14763B)


      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 <cstdint>
     18 
     19 #include "absl/base/config.h"
     20 #include "absl/base/internal/spinlock.h"
     21 #include "absl/container/inlined_vector.h"
     22 #include "absl/debugging/stacktrace.h"
     23 #include "absl/strings/internal/cord_internal.h"
     24 #include "absl/strings/internal/cord_rep_btree.h"
     25 #include "absl/strings/internal/cord_rep_crc.h"
     26 #include "absl/strings/internal/cordz_handle.h"
     27 #include "absl/strings/internal/cordz_statistics.h"
     28 #include "absl/strings/internal/cordz_update_tracker.h"
     29 #include "absl/synchronization/mutex.h"
     30 #include "absl/time/clock.h"
     31 #include "absl/types/span.h"
     32 
     33 namespace absl {
     34 ABSL_NAMESPACE_BEGIN
     35 namespace cord_internal {
     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 // subtract 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`.
     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.
     76  void AnalyzeCordRep(const CordRep* rep) {
     77    ABSL_ASSERT(rep != nullptr);
     78 
     79    // Process all linear nodes.
     80    // As per the class comments, use refcout - 1 on the top level node, as the
     81    // top level node is assumed to be referenced only for analysis purposes.
     82    size_t refcount = rep->refcount.Get();
     83    RepRef repref{rep, (refcount > 1) ? refcount - 1 : 1};
     84 
     85    // Process the top level CRC node, if present.
     86    if (repref.tag() == CRC) {
     87      statistics_.node_count++;
     88      statistics_.node_counts.crc++;
     89      memory_usage_.Add(sizeof(CordRepCrc), repref.refcount);
     90      repref = repref.Child(repref.rep->crc()->child);
     91    }
     92 
     93    // Process all top level linear nodes (substrings and flats).
     94    repref = CountLinearReps(repref, memory_usage_);
     95 
     96    switch (repref.tag()) {
     97      case CordRepKind::BTREE:
     98        AnalyzeBtree(repref);
     99        break;
    100      default:
    101        // We should have a btree node if not null.
    102        ABSL_ASSERT(repref.tag() == CordRepKind::UNUSED_0);
    103        break;
    104    }
    105 
    106    // Adds values to output
    107    statistics_.estimated_memory_usage += memory_usage_.total;
    108    statistics_.estimated_fair_share_memory_usage +=
    109        static_cast<size_t>(memory_usage_.fair_share);
    110  }
    111 
    112 private:
    113  // RepRef identifies a CordRep* inside the Cord tree with its cumulative
    114  // refcount including itself. For example, a tree consisting of a substring
    115  // with a refcount of 3 and a child flat with a refcount of 4 will have RepRef
    116  // refcounts of 3 and 12 respectively.
    117  struct RepRef {
    118    const CordRep* rep;
    119    size_t refcount;
    120 
    121    // Returns a 'child' RepRef which contains the cumulative reference count
    122    // of this instance multiplied by the child's reference count. Returns a
    123    // nullptr RepRef value with a refcount of 0 if `child` is nullptr.
    124    RepRef Child(const CordRep* child) const {
    125      if (child == nullptr) return RepRef{nullptr, 0};
    126      return RepRef{child, refcount * child->refcount.Get()};
    127    }
    128 
    129    // Returns the tag of this rep, or UNUSED_0 if this instance is null
    130    constexpr CordRepKind tag() const {
    131      ABSL_ASSERT(rep == nullptr || rep->tag != CordRepKind::UNUSED_0);
    132      return rep ? static_cast<CordRepKind>(rep->tag) : CordRepKind::UNUSED_0;
    133    }
    134  };
    135 
    136  // Memory usage values
    137  struct MemoryUsage {
    138    size_t total = 0;
    139    double fair_share = 0.0;
    140 
    141    // Adds 'size` memory usage to this class, with a cumulative (recursive)
    142    // reference count of `refcount`
    143    void Add(size_t size, size_t refcount) {
    144      total += size;
    145      fair_share += static_cast<double>(size) / refcount;
    146    }
    147  };
    148 
    149  // Counts a flat of the provide allocated size
    150  void CountFlat(size_t size) {
    151    statistics_.node_count++;
    152    statistics_.node_counts.flat++;
    153    if (size <= 64) {
    154      statistics_.node_counts.flat_64++;
    155    } else if (size <= 128) {
    156      statistics_.node_counts.flat_128++;
    157    } else if (size <= 256) {
    158      statistics_.node_counts.flat_256++;
    159    } else if (size <= 512) {
    160      statistics_.node_counts.flat_512++;
    161    } else if (size <= 1024) {
    162      statistics_.node_counts.flat_1k++;
    163    }
    164  }
    165 
    166  // Processes 'linear' reps (substring, flat, external) not requiring iteration
    167  // or recursion. Returns RefRep{null} if all reps were processed, else returns
    168  // the top-most non-linear concat or ring cordrep.
    169  // Node counts are updated into `statistics_`, memory usage is update into
    170  // `memory_usage`, which typically references `memory_usage_` except for ring
    171  // buffers where we count children unrounded.
    172  RepRef CountLinearReps(RepRef rep, MemoryUsage& memory_usage) {
    173    // Consume all substrings
    174    while (rep.tag() == SUBSTRING) {
    175      statistics_.node_count++;
    176      statistics_.node_counts.substring++;
    177      memory_usage.Add(sizeof(CordRepSubstring), rep.refcount);
    178      rep = rep.Child(rep.rep->substring()->child);
    179    }
    180 
    181    // Consume possible FLAT
    182    if (rep.tag() >= FLAT) {
    183      size_t size = rep.rep->flat()->AllocatedSize();
    184      CountFlat(size);
    185      memory_usage.Add(size, rep.refcount);
    186      return RepRef{nullptr, 0};
    187    }
    188 
    189    // Consume possible external
    190    if (rep.tag() == EXTERNAL) {
    191      statistics_.node_count++;
    192      statistics_.node_counts.external++;
    193      size_t size = rep.rep->length + sizeof(CordRepExternalImpl<intptr_t>);
    194      memory_usage.Add(size, rep.refcount);
    195      return RepRef{nullptr, 0};
    196    }
    197 
    198    return rep;
    199  }
    200 
    201  // Analyzes the provided btree.
    202  void AnalyzeBtree(RepRef rep) {
    203    statistics_.node_count++;
    204    statistics_.node_counts.btree++;
    205    memory_usage_.Add(sizeof(CordRepBtree), rep.refcount);
    206    const CordRepBtree* tree = rep.rep->btree();
    207    if (tree->height() > 0) {
    208      for (CordRep* edge : tree->Edges()) {
    209        AnalyzeBtree(rep.Child(edge));
    210      }
    211    } else {
    212      for (CordRep* edge : tree->Edges()) {
    213        CountLinearReps(rep.Child(edge), memory_usage_);
    214      }
    215    }
    216  }
    217 
    218  CordzStatistics& statistics_;
    219  MemoryUsage memory_usage_;
    220 };
    221 
    222 }  // namespace
    223 
    224 CordzInfo* CordzInfo::Head(const CordzSnapshot& snapshot) {
    225  ABSL_ASSERT(snapshot.is_snapshot());
    226 
    227  // We can do an 'unsafe' load of 'head', as we are guaranteed that the
    228  // instance it points to is kept alive by the provided CordzSnapshot, so we
    229  // can simply return the current value using an acquire load.
    230  // We do enforce in DEBUG builds that the 'head' value is present in the
    231  // delete queue: ODR violations may lead to 'snapshot' and 'global_list_'
    232  // being in different libraries / modules.
    233  CordzInfo* head = global_list_.head.load(std::memory_order_acquire);
    234  ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(head));
    235  return head;
    236 }
    237 
    238 CordzInfo* CordzInfo::Next(const CordzSnapshot& snapshot) const {
    239  ABSL_ASSERT(snapshot.is_snapshot());
    240 
    241  // Similar to the 'Head()' function, we do not need a mutex here.
    242  CordzInfo* next = ci_next_.load(std::memory_order_acquire);
    243  ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(this));
    244  ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(next));
    245  return next;
    246 }
    247 
    248 void CordzInfo::TrackCord(InlineData& cord, MethodIdentifier method,
    249                          int64_t sampling_stride) {
    250  assert(cord.is_tree());
    251  assert(!cord.is_profiled());
    252  CordzInfo* cordz_info =
    253      new CordzInfo(cord.as_tree(), nullptr, method, sampling_stride);
    254  cord.set_cordz_info(cordz_info);
    255  cordz_info->Track();
    256 }
    257 
    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                             src.cordz_info()->sampling_stride());
    271  cord.set_cordz_info(cordz_info);
    272  cordz_info->Track();
    273 }
    274 
    275 void CordzInfo::MaybeTrackCordImpl(InlineData& cord, const InlineData& src,
    276                                   MethodIdentifier method) {
    277  if (src.is_profiled()) {
    278    TrackCord(cord, src, method);
    279  } else if (cord.is_profiled()) {
    280    cord.cordz_info()->Untrack();
    281    cord.clear_cordz_info();
    282  }
    283 }
    284 
    285 CordzInfo::MethodIdentifier CordzInfo::GetParentMethod(const CordzInfo* src) {
    286  if (src == nullptr) return MethodIdentifier::kUnknown;
    287  return src->parent_method_ != MethodIdentifier::kUnknown ? src->parent_method_
    288                                                           : src->method_;
    289 }
    290 
    291 size_t CordzInfo::FillParentStack(const CordzInfo* src, void** stack) {
    292  assert(stack);
    293  if (src == nullptr) return 0;
    294  if (src->parent_stack_depth_) {
    295    memcpy(stack, src->parent_stack_, src->parent_stack_depth_ * sizeof(void*));
    296    return src->parent_stack_depth_;
    297  }
    298  memcpy(stack, src->stack_, src->stack_depth_ * sizeof(void*));
    299  return src->stack_depth_;
    300 }
    301 
    302 CordzInfo::CordzInfo(CordRep* rep, const CordzInfo* src,
    303                     MethodIdentifier method, int64_t sampling_stride)
    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      sampling_stride_(sampling_stride) {
    314  update_tracker_.LossyAdd(method);
    315  if (src) {
    316    // Copy parent counters.
    317    update_tracker_.LossyAdd(src->update_tracker_);
    318  }
    319 }
    320 
    321 CordzInfo::~CordzInfo() {
    322  // `rep_` is potentially kept alive if CordzInfo is included
    323  // in a collection snapshot (which should be rare).
    324  if (ABSL_PREDICT_FALSE(rep_)) {
    325    CordRep::Unref(rep_);
    326  }
    327 }
    328 
    329 void CordzInfo::Track() {
    330  SpinLockHolder l(&list_->mutex);
    331 
    332  CordzInfo* const head = list_->head.load(std::memory_order_acquire);
    333  if (head != nullptr) {
    334    head->ci_prev_.store(this, std::memory_order_release);
    335  }
    336  ci_next_.store(head, std::memory_order_release);
    337  list_->head.store(this, std::memory_order_release);
    338 }
    339 
    340 void CordzInfo::Untrack() {
    341  ODRCheck();
    342  {
    343    SpinLockHolder l(&list_->mutex);
    344 
    345    CordzInfo* const head = list_->head.load(std::memory_order_acquire);
    346    CordzInfo* const next = ci_next_.load(std::memory_order_acquire);
    347    CordzInfo* const prev = ci_prev_.load(std::memory_order_acquire);
    348 
    349    if (next) {
    350      ABSL_ASSERT(next->ci_prev_.load(std::memory_order_acquire) == this);
    351      next->ci_prev_.store(prev, std::memory_order_release);
    352    }
    353    if (prev) {
    354      ABSL_ASSERT(head != this);
    355      ABSL_ASSERT(prev->ci_next_.load(std::memory_order_acquire) == this);
    356      prev->ci_next_.store(next, std::memory_order_release);
    357    } else {
    358      ABSL_ASSERT(head == this);
    359      list_->head.store(next, std::memory_order_release);
    360    }
    361  }
    362 
    363  // We can no longer be discovered: perform a fast path check if we are not
    364  // listed on any delete queue, so we can directly delete this instance.
    365  if (SafeToDelete()) {
    366    UnsafeSetCordRep(nullptr);
    367    delete this;
    368    return;
    369  }
    370 
    371  // We are likely part of a snapshot, extend the life of the CordRep
    372  {
    373    absl::MutexLock lock(&mutex_);
    374    if (rep_) CordRep::Ref(rep_);
    375  }
    376  CordzHandle::Delete(this);
    377 }
    378 
    379 void CordzInfo::Lock(MethodIdentifier method)
    380    ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex_) {
    381  mutex_.Lock();
    382  update_tracker_.LossyAdd(method);
    383  assert(rep_);
    384 }
    385 
    386 void CordzInfo::Unlock() ABSL_UNLOCK_FUNCTION(mutex_) {
    387  bool tracked = rep_ != nullptr;
    388  mutex_.Unlock();
    389  if (!tracked) {
    390    Untrack();
    391  }
    392 }
    393 
    394 absl::Span<void* const> CordzInfo::GetStack() const {
    395  return absl::MakeConstSpan(stack_, stack_depth_);
    396 }
    397 
    398 absl::Span<void* const> CordzInfo::GetParentStack() const {
    399  return absl::MakeConstSpan(parent_stack_, parent_stack_depth_);
    400 }
    401 
    402 CordzStatistics CordzInfo::GetCordzStatistics() const {
    403  CordzStatistics stats;
    404  stats.method = method_;
    405  stats.parent_method = parent_method_;
    406  stats.update_tracker = update_tracker_;
    407  if (CordRep* rep = RefCordRep()) {
    408    stats.size = rep->length;
    409    CordRepAnalyzer analyzer(stats);
    410    analyzer.AnalyzeCordRep(rep);
    411    CordRep::Unref(rep);
    412  }
    413  return stats;
    414 }
    415 
    416 }  // namespace cord_internal
    417 ABSL_NAMESPACE_END
    418 }  // namespace absl