1 // Copyright 2018 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/container/internal/hashtablez_sampler.h"
16
17 #include <algorithm>
18 #include <atomic>
19 #include <cassert>
20 #include <cmath>
21 #include <functional>
22 #include <limits>
23
24 #include "absl/base/attributes.h"
25 #include "absl/base/config.h"
26 #include "absl/debugging/stacktrace.h"
27 #include "absl/memory/memory.h"
28 #include "absl/profiling/internal/exponential_biased.h"
29 #include "absl/profiling/internal/sample_recorder.h"
30 #include "absl/synchronization/mutex.h"
31 #include "absl/utility/utility.h"
32
33 namespace absl {
34 ABSL_NAMESPACE_BEGIN
35 namespace container_internal {
36
37 #ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
38 constexpr int HashtablezInfo::kMaxStackDepth;
39 #endif
40
41 namespace {
42 ABSL_CONST_INIT std::atomic<bool> g_hashtablez_enabled{
43 false
44 };
45 ABSL_CONST_INIT std::atomic<int32_t> g_hashtablez_sample_parameter{1 << 10};
46 std::atomic<HashtablezConfigListener> g_hashtablez_config_listener{nullptr};
47
48 #if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
49 ABSL_PER_THREAD_TLS_KEYWORD absl::profiling_internal::ExponentialBiased
50 g_exponential_biased_generator;
51 #endif
52
TriggerHashtablezConfigListener()53 void TriggerHashtablezConfigListener() {
54 auto* listener = g_hashtablez_config_listener.load(std::memory_order_acquire);
55 if (listener != nullptr) listener();
56 }
57
58 } // namespace
59
60 #if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
61 ABSL_PER_THREAD_TLS_KEYWORD SamplingState global_next_sample = {0, 0};
62 #endif // defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
63
GlobalHashtablezSampler()64 HashtablezSampler& GlobalHashtablezSampler() {
65 static auto* sampler = new HashtablezSampler();
66 return *sampler;
67 }
68
69 HashtablezInfo::HashtablezInfo() = default;
70 HashtablezInfo::~HashtablezInfo() = default;
71
PrepareForSampling(int64_t stride,size_t inline_element_size_value)72 void HashtablezInfo::PrepareForSampling(int64_t stride,
73 size_t inline_element_size_value) {
74 capacity.store(0, std::memory_order_relaxed);
75 size.store(0, std::memory_order_relaxed);
76 num_erases.store(0, std::memory_order_relaxed);
77 num_rehashes.store(0, std::memory_order_relaxed);
78 max_probe_length.store(0, std::memory_order_relaxed);
79 total_probe_length.store(0, std::memory_order_relaxed);
80 hashes_bitwise_or.store(0, std::memory_order_relaxed);
81 hashes_bitwise_and.store(~size_t{}, std::memory_order_relaxed);
82 hashes_bitwise_xor.store(0, std::memory_order_relaxed);
83 max_reserve.store(0, std::memory_order_relaxed);
84
85 create_time = absl::Now();
86 weight = stride;
87 // The inliner makes hardcoded skip_count difficult (especially when combined
88 // with LTO). We use the ability to exclude stacks by regex when encoding
89 // instead.
90 depth = absl::GetStackTrace(stack, HashtablezInfo::kMaxStackDepth,
91 /* skip_count= */ 0);
92 inline_element_size = inline_element_size_value;
93 }
94
ShouldForceSampling()95 static bool ShouldForceSampling() {
96 enum ForceState {
97 kDontForce,
98 kForce,
99 kUninitialized
100 };
101 ABSL_CONST_INIT static std::atomic<ForceState> global_state{
102 kUninitialized};
103 ForceState state = global_state.load(std::memory_order_relaxed);
104 if (ABSL_PREDICT_TRUE(state == kDontForce)) return false;
105
106 if (state == kUninitialized) {
107 state = ABSL_INTERNAL_C_SYMBOL(AbslContainerInternalSampleEverything)()
108 ? kForce
109 : kDontForce;
110 global_state.store(state, std::memory_order_relaxed);
111 }
112 return state == kForce;
113 }
114
SampleSlow(SamplingState & next_sample,size_t inline_element_size)115 HashtablezInfo* SampleSlow(SamplingState& next_sample,
116 size_t inline_element_size) {
117 if (ABSL_PREDICT_FALSE(ShouldForceSampling())) {
118 next_sample.next_sample = 1;
119 const int64_t old_stride = exchange(next_sample.sample_stride, 1);
120 HashtablezInfo* result =
121 GlobalHashtablezSampler().Register(old_stride, inline_element_size);
122 return result;
123 }
124
125 #if !defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
126 next_sample = {
127 std::numeric_limits<int64_t>::max(),
128 std::numeric_limits<int64_t>::max(),
129 };
130 return nullptr;
131 #else
132 bool first = next_sample.next_sample < 0;
133
134 const int64_t next_stride = g_exponential_biased_generator.GetStride(
135 g_hashtablez_sample_parameter.load(std::memory_order_relaxed));
136
137 next_sample.next_sample = next_stride;
138 const int64_t old_stride = exchange(next_sample.sample_stride, next_stride);
139 // Small values of interval are equivalent to just sampling next time.
140 ABSL_ASSERT(next_stride >= 1);
141
142 // g_hashtablez_enabled can be dynamically flipped, we need to set a threshold
143 // low enough that we will start sampling in a reasonable time, so we just use
144 // the default sampling rate.
145 if (!g_hashtablez_enabled.load(std::memory_order_relaxed)) return nullptr;
146
147 // We will only be negative on our first count, so we should just retry in
148 // that case.
149 if (first) {
150 if (ABSL_PREDICT_TRUE(--next_sample.next_sample > 0)) return nullptr;
151 return SampleSlow(next_sample, inline_element_size);
152 }
153
154 return GlobalHashtablezSampler().Register(old_stride, inline_element_size);
155 #endif
156 }
157
UnsampleSlow(HashtablezInfo * info)158 void UnsampleSlow(HashtablezInfo* info) {
159 GlobalHashtablezSampler().Unregister(info);
160 }
161
RecordRehashSlow(HashtablezInfo * info,size_t total_probe_length)162 void RecordRehashSlow(HashtablezInfo* info, size_t total_probe_length) {
163 #ifdef ABSL_INTERNAL_HAVE_SSE2
164 total_probe_length /= 16;
165 #else
166 total_probe_length /= 8;
167 #endif
168 info->total_probe_length.store(total_probe_length, std::memory_order_relaxed);
169 info->num_erases.store(0, std::memory_order_relaxed);
170 // There is only one concurrent writer, so `load` then `store` is sufficient
171 // instead of using `fetch_add`.
172 info->num_rehashes.store(
173 1 + info->num_rehashes.load(std::memory_order_relaxed),
174 std::memory_order_relaxed);
175 }
176
RecordReservationSlow(HashtablezInfo * info,size_t target_capacity)177 void RecordReservationSlow(HashtablezInfo* info, size_t target_capacity) {
178 info->max_reserve.store(
179 (std::max)(info->max_reserve.load(std::memory_order_relaxed),
180 target_capacity),
181 std::memory_order_relaxed);
182 }
183
RecordClearedReservationSlow(HashtablezInfo * info)184 void RecordClearedReservationSlow(HashtablezInfo* info) {
185 info->max_reserve.store(0, std::memory_order_relaxed);
186 }
187
RecordStorageChangedSlow(HashtablezInfo * info,size_t size,size_t capacity)188 void RecordStorageChangedSlow(HashtablezInfo* info, size_t size,
189 size_t capacity) {
190 info->size.store(size, std::memory_order_relaxed);
191 info->capacity.store(capacity, std::memory_order_relaxed);
192 if (size == 0) {
193 // This is a clear, reset the total/num_erases too.
194 info->total_probe_length.store(0, std::memory_order_relaxed);
195 info->num_erases.store(0, std::memory_order_relaxed);
196 }
197 }
198
RecordInsertSlow(HashtablezInfo * info,size_t hash,size_t distance_from_desired)199 void RecordInsertSlow(HashtablezInfo* info, size_t hash,
200 size_t distance_from_desired) {
201 // SwissTables probe in groups of 16, so scale this to count items probes and
202 // not offset from desired.
203 size_t probe_length = distance_from_desired;
204 #ifdef ABSL_INTERNAL_HAVE_SSE2
205 probe_length /= 16;
206 #else
207 probe_length /= 8;
208 #endif
209
210 info->hashes_bitwise_and.fetch_and(hash, std::memory_order_relaxed);
211 info->hashes_bitwise_or.fetch_or(hash, std::memory_order_relaxed);
212 info->hashes_bitwise_xor.fetch_xor(hash, std::memory_order_relaxed);
213 info->max_probe_length.store(
214 std::max(info->max_probe_length.load(std::memory_order_relaxed),
215 probe_length),
216 std::memory_order_relaxed);
217 info->total_probe_length.fetch_add(probe_length, std::memory_order_relaxed);
218 info->size.fetch_add(1, std::memory_order_relaxed);
219 }
220
RecordEraseSlow(HashtablezInfo * info)221 void RecordEraseSlow(HashtablezInfo* info) {
222 info->size.fetch_sub(1, std::memory_order_relaxed);
223 // There is only one concurrent writer, so `load` then `store` is sufficient
224 // instead of using `fetch_add`.
225 info->num_erases.store(1 + info->num_erases.load(std::memory_order_relaxed),
226 std::memory_order_relaxed);
227 }
228
SetHashtablezConfigListener(HashtablezConfigListener l)229 void SetHashtablezConfigListener(HashtablezConfigListener l) {
230 g_hashtablez_config_listener.store(l, std::memory_order_release);
231 }
232
IsHashtablezEnabled()233 bool IsHashtablezEnabled() {
234 return g_hashtablez_enabled.load(std::memory_order_acquire);
235 }
236
SetHashtablezEnabled(bool enabled)237 void SetHashtablezEnabled(bool enabled) {
238 SetHashtablezEnabledInternal(enabled);
239 TriggerHashtablezConfigListener();
240 }
241
SetHashtablezEnabledInternal(bool enabled)242 void SetHashtablezEnabledInternal(bool enabled) {
243 g_hashtablez_enabled.store(enabled, std::memory_order_release);
244 }
245
GetHashtablezSampleParameter()246 int32_t GetHashtablezSampleParameter() {
247 return g_hashtablez_sample_parameter.load(std::memory_order_acquire);
248 }
249
SetHashtablezSampleParameter(int32_t rate)250 void SetHashtablezSampleParameter(int32_t rate) {
251 SetHashtablezSampleParameterInternal(rate);
252 TriggerHashtablezConfigListener();
253 }
254
SetHashtablezSampleParameterInternal(int32_t rate)255 void SetHashtablezSampleParameterInternal(int32_t rate) {
256 if (rate > 0) {
257 g_hashtablez_sample_parameter.store(rate, std::memory_order_release);
258 } else {
259 ABSL_RAW_LOG(ERROR, "Invalid hashtablez sample rate: %lld",
260 static_cast<long long>(rate)); // NOLINT(runtime/int)
261 }
262 }
263
GetHashtablezMaxSamples()264 size_t GetHashtablezMaxSamples() {
265 return GlobalHashtablezSampler().GetMaxSamples();
266 }
267
SetHashtablezMaxSamples(size_t max)268 void SetHashtablezMaxSamples(size_t max) {
269 SetHashtablezMaxSamplesInternal(max);
270 TriggerHashtablezConfigListener();
271 }
272
SetHashtablezMaxSamplesInternal(size_t max)273 void SetHashtablezMaxSamplesInternal(size_t max) {
274 if (max > 0) {
275 GlobalHashtablezSampler().SetMaxSamples(max);
276 } else {
277 ABSL_RAW_LOG(ERROR, "Invalid hashtablez max samples: 0");
278 }
279 }
280
281 } // namespace container_internal
282 ABSL_NAMESPACE_END
283 } // namespace absl
284