1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/profiler/metadata_recorder.h"
7 #include "base/metrics/histogram_macros.h"
8 #include "third_party/abseil-cpp/absl/types/optional.h"
12 const size_t MetadataRecorder::MAX_METADATA_COUNT;
14 MetadataRecorder::Item::Item(uint64_t name_hash,
15 absl::optional<int64_t> key,
16 absl::optional<PlatformThreadId> thread_id,
18 : name_hash(name_hash), key(key), thread_id(thread_id), value(value) {}
20 MetadataRecorder::Item::Item() : name_hash(0), value(0) {}
22 MetadataRecorder::Item::Item(const Item& other) = default;
24 MetadataRecorder::Item& MetadataRecorder::Item::Item::operator=(
25 const Item& other) = default;
27 MetadataRecorder::ItemInternal::ItemInternal() = default;
29 MetadataRecorder::ItemInternal::~ItemInternal() = default;
31 MetadataRecorder::MetadataRecorder() {
32 // Ensure that we have necessary atomic support.
33 DCHECK(items_[0].is_active.is_lock_free());
34 DCHECK(items_[0].value.is_lock_free());
37 MetadataRecorder::~MetadataRecorder() = default;
39 void MetadataRecorder::Set(uint64_t name_hash,
40 absl::optional<int64_t> key,
41 absl::optional<PlatformThreadId> thread_id,
43 AutoLock lock(write_lock_);
45 // Acquiring the |write_lock_| ensures that:
47 // - We don't try to write into the same new slot at the same time as
49 // - We see all writes by other threads (acquiring a mutex implies acquire
51 size_t item_slots_used = item_slots_used_.load(std::memory_order_relaxed);
52 for (size_t i = 0; i < item_slots_used; ++i) {
53 auto& item = items_[i];
54 if (item.name_hash == name_hash && item.key == key &&
55 item.thread_id == thread_id) {
56 item.value.store(value, std::memory_order_relaxed);
58 const bool was_active =
59 item.is_active.exchange(true, std::memory_order_release);
61 inactive_item_count_--;
67 item_slots_used = TryReclaimInactiveSlots(item_slots_used);
69 if (item_slots_used == items_.size()) {
70 // The metadata recorder is full, forcing us to drop this metadata. The
71 // above UMA histogram counting occupied metadata slots should help us set a
72 // max size that avoids this condition during normal Chrome use.
76 // Wait until the item is fully created before setting |is_active| to true and
77 // incrementing |item_slots_used_|, which will signal to readers that the item
79 auto& item = items_[item_slots_used];
80 item.name_hash = name_hash;
82 item.thread_id = thread_id;
83 item.value.store(value, std::memory_order_relaxed);
84 item.is_active.store(true, std::memory_order_release);
85 item_slots_used_.fetch_add(1, std::memory_order_release);
88 void MetadataRecorder::Remove(uint64_t name_hash,
89 absl::optional<int64_t> key,
90 absl::optional<PlatformThreadId> thread_id) {
91 AutoLock lock(write_lock_);
93 size_t item_slots_used = item_slots_used_.load(std::memory_order_relaxed);
94 for (size_t i = 0; i < item_slots_used; ++i) {
95 auto& item = items_[i];
96 if (item.name_hash == name_hash && item.key == key &&
97 item.thread_id == thread_id) {
98 // A removed item will occupy its slot until that slot is reclaimed.
99 const bool was_active =
100 item.is_active.exchange(false, std::memory_order_relaxed);
102 inactive_item_count_++;
109 MetadataRecorder::MetadataProvider::MetadataProvider(
110 MetadataRecorder* metadata_recorder,
111 PlatformThreadId thread_id)
112 : metadata_recorder_(metadata_recorder),
113 thread_id_(thread_id),
114 auto_lock_(metadata_recorder->read_lock_) {}
116 MetadataRecorder::MetadataProvider::~MetadataProvider() = default;
118 size_t MetadataRecorder::MetadataProvider::GetItems(
119 ItemArray* const items) const {
120 return metadata_recorder_->GetItems(items, thread_id_);
123 size_t MetadataRecorder::GetItems(ItemArray* const items,
124 PlatformThreadId thread_id) const {
125 // If a writer adds a new item after this load, it will be ignored. We do
126 // this instead of calling item_slots_used_.load() explicitly in the for loop
127 // bounds checking, which would be expensive.
129 // Also note that items are snapshotted sequentially and that items can be
130 // modified mid-snapshot by non-suspended threads. This means that there's a
131 // small chance that some items, especially those that occur later in the
132 // array, may have values slightly "in the future" from when the sample was
133 // actually collected. It also means that the array as returned may have never
134 // existed in its entirety, although each name/value pair represents a
135 // consistent item that existed very shortly after the thread was supended.
136 size_t item_slots_used = item_slots_used_.load(std::memory_order_acquire);
137 size_t write_index = 0;
138 for (size_t read_index = 0; read_index < item_slots_used; ++read_index) {
139 const auto& item = items_[read_index];
140 // Because we wait until |is_active| is set to consider an item active and
141 // that field is always set last, we ignore half-created items.
142 if (item.is_active.load(std::memory_order_acquire) &&
143 (!item.thread_id.has_value() || item.thread_id == thread_id)) {
144 (*items)[write_index++] =
145 Item{item.name_hash, item.key, item.thread_id,
146 item.value.load(std::memory_order_relaxed)};
153 size_t MetadataRecorder::TryReclaimInactiveSlots(size_t item_slots_used) {
154 const size_t remaining_slots = MAX_METADATA_COUNT - item_slots_used;
156 if (inactive_item_count_ == 0 || inactive_item_count_ < remaining_slots) {
157 // This reclaiming threshold has a few nice properties:
159 // - It avoids reclaiming when no items have been removed
160 // - It makes doing so more likely as free slots become more scarce
161 // - It makes doing so less likely when the benefits are lower
162 return item_slots_used;
165 if (read_lock_.Try()) {
166 // The lock isn't already held by a reader or another thread reclaiming
168 item_slots_used = ReclaimInactiveSlots(item_slots_used);
169 read_lock_.Release();
172 return item_slots_used;
175 size_t MetadataRecorder::ReclaimInactiveSlots(size_t item_slots_used) {
176 // From here until the end of the reclamation, we can safely use
177 // memory_order_relaxed for all reads and writes. We don't need
178 // memory_order_acquire because acquiring the write mutex gives acquire
179 // semantics and no other threads can write after we hold that mutex. We don't
180 // need memory_order_release because no readers can read until we release the
181 // read mutex, which itself has release semantics.
182 size_t first_inactive_item_idx = 0;
183 size_t last_active_item_idx = item_slots_used - 1;
184 while (first_inactive_item_idx < last_active_item_idx) {
185 ItemInternal& inactive_item = items_[first_inactive_item_idx];
186 ItemInternal& active_item = items_[last_active_item_idx];
188 if (inactive_item.is_active.load(std::memory_order_relaxed)) {
189 // Keep seeking forward to an inactive item.
190 ++first_inactive_item_idx;
194 if (!active_item.is_active.load(std::memory_order_relaxed)) {
195 // Keep seeking backward to an active item. Skipping over this item
196 // indicates that we're freeing the slot at this index.
197 --last_active_item_idx;
202 inactive_item.name_hash = active_item.name_hash;
203 inactive_item.value.store(active_item.value.load(std::memory_order_relaxed),
204 std::memory_order_relaxed);
205 inactive_item.is_active.store(true, std::memory_order_relaxed);
207 ++first_inactive_item_idx;
208 --last_active_item_idx;
212 item_slots_used_.store(item_slots_used, std::memory_order_relaxed);
213 return item_slots_used;