Fix emulator build error
[platform/framework/web/chromium-efl.git] / base / profiler / metadata_recorder.cc
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.
4
5 #include "base/profiler/metadata_recorder.h"
6
7 #include "base/metrics/histogram_macros.h"
8 #include "third_party/abseil-cpp/absl/types/optional.h"
9
10 namespace base {
11
12 const size_t MetadataRecorder::MAX_METADATA_COUNT;
13
14 MetadataRecorder::Item::Item(uint64_t name_hash,
15                              absl::optional<int64_t> key,
16                              absl::optional<PlatformThreadId> thread_id,
17                              int64_t value)
18     : name_hash(name_hash), key(key), thread_id(thread_id), value(value) {}
19
20 MetadataRecorder::Item::Item() : name_hash(0), value(0) {}
21
22 MetadataRecorder::Item::Item(const Item& other) = default;
23
24 MetadataRecorder::Item& MetadataRecorder::Item::Item::operator=(
25     const Item& other) = default;
26
27 MetadataRecorder::ItemInternal::ItemInternal() = default;
28
29 MetadataRecorder::ItemInternal::~ItemInternal() = default;
30
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());
35 }
36
37 MetadataRecorder::~MetadataRecorder() = default;
38
39 void MetadataRecorder::Set(uint64_t name_hash,
40                            absl::optional<int64_t> key,
41                            absl::optional<PlatformThreadId> thread_id,
42                            int64_t value) {
43   AutoLock lock(write_lock_);
44
45   // Acquiring the |write_lock_| ensures that:
46   //
47   //   - We don't try to write into the same new slot at the same time as
48   //     another thread
49   //   - We see all writes by other threads (acquiring a mutex implies acquire
50   //     semantics)
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);
57
58       const bool was_active =
59           item.is_active.exchange(true, std::memory_order_release);
60       if (!was_active)
61         inactive_item_count_--;
62
63       return;
64     }
65   }
66
67   item_slots_used = TryReclaimInactiveSlots(item_slots_used);
68
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.
73     return;
74   }
75
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
78   // is ready.
79   auto& item = items_[item_slots_used];
80   item.name_hash = name_hash;
81   item.key = key;
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);
86 }
87
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_);
92
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);
101       if (was_active)
102         inactive_item_count_++;
103
104       return;
105     }
106   }
107 }
108
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_) {}
115
116 MetadataRecorder::MetadataProvider::~MetadataProvider() = default;
117
118 size_t MetadataRecorder::MetadataProvider::GetItems(
119     ItemArray* const items) const {
120   return metadata_recorder_->GetItems(items, thread_id_);
121 }
122
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.
128   //
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)};
147     }
148   }
149
150   return write_index;
151 }
152
153 size_t MetadataRecorder::TryReclaimInactiveSlots(size_t item_slots_used) {
154   const size_t remaining_slots = MAX_METADATA_COUNT - item_slots_used;
155
156   if (inactive_item_count_ == 0 || inactive_item_count_ < remaining_slots) {
157     // This reclaiming threshold has a few nice properties:
158     //
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;
163   }
164
165   if (read_lock_.Try()) {
166     // The lock isn't already held by a reader or another thread reclaiming
167     // slots.
168     item_slots_used = ReclaimInactiveSlots(item_slots_used);
169     read_lock_.Release();
170   }
171
172   return item_slots_used;
173 }
174
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];
187
188     if (inactive_item.is_active.load(std::memory_order_relaxed)) {
189       // Keep seeking forward to an inactive item.
190       ++first_inactive_item_idx;
191       continue;
192     }
193
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;
198       item_slots_used--;
199       continue;
200     }
201
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);
206
207     ++first_inactive_item_idx;
208     --last_active_item_idx;
209     item_slots_used--;
210   }
211
212   item_slots_used_.store(item_slots_used, std::memory_order_relaxed);
213   return item_slots_used;
214 }
215 }  // namespace base