Seastar
High performance C++ framework for concurrent servers
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages
sampler.hh
1/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17// This file has been originally been imported from:
18// https://cs.android.com/android/platform/superproject/+/013901367630d3ec71c9f2bb3f3077bd11585301:external/perfetto/src/profiling/memory/sampler.h
19
20//
21// The code has been modified as follows:
22//
23// - Integrated into seastar and adapted to coding style
24// - Right now we don't account for samples multiple times (in case we have
25// multiple loops of drawing from the exp distribution). The reason is that
26// in our memory sampler we would have to store the weight in addition to the
27// alloation site ptr as on free we need to know how much a sample accounted
28// for. Hence, for now we simply always use the sampling interval.
29// - Sampler can be turned "off" with a 0 sampling rate
30// - The fast path is more optimized (as a consequence of the first point)
31// - Provide a way to temporarily pause sampling
32//
33// Changes Copyright (C) 2023 ScyllaDB
34
35#pragma once
36
37#include <random>
38
39// See also: https://perfetto.dev/docs/design-docs/heapprofd-sampling for more
40// background of how the sampler works
41
42class sampler {
43public:
44 sampler() : random_gen(rd_device()) {
46 }
48 void set_sampling_interval(uint64_t sampling_interval) {
49 sampling_interval_ = sampling_interval;
50 if (sampling_interval_ == 0) {
51 // Set the interval very large. This means in practice we will
52 // likely never get this below zero and hence it's unlikely we will
53 // ever have to run the reset path with sampling off
54 interval_to_next_sample_ = std::numeric_limits<int64_t>::max();
55 return;
56 }
57 sampling_rate_ = 1.0 / static_cast<double>(sampling_interval_);
58 interval_to_next_sample_ = next_sampling_interval();
59 }
66 [[gnu::always_inline]]
67 bool maybe_sample(size_t alloc_size) {
68 return (interval_to_next_sample_ -= alloc_size) < 0;
69 }
70
74 bool definitely_sample(size_t alloc_size) {
75 // this will hold if maybe_sample returned false for this allocation
76 if (interval_to_next_sample_ >= 0) {
77 return false;
78 }
79 reset_interval_to_next_sample(alloc_size);
80 return sampling_interval_ != 0; // sampling interval 0 means off
81 }
82
83 uint64_t sampling_interval() const { return sampling_interval_; }
84
86 size_t sample_size(size_t allocation_size) const {
87 return std::max(allocation_size, sampling_interval_);
88 }
89
94 : sampler_(&sampler)
95 , previous_sampling_interval_(sampler_->sampling_interval_)
96 , previous_sampling_rate_(sampler_->sampling_rate_)
97 , previous_interval_to_next_sample_(sampler_->interval_to_next_sample_) {
98 sampler_->set_sampling_interval(0);
99 }
100
102 if (sampler_) {
103 sampler_->sampling_interval_ = previous_sampling_interval_;
104 sampler_->sampling_rate_ = previous_sampling_rate_;
105 sampler_->interval_to_next_sample_ = previous_interval_to_next_sample_;
106 }
107 }
108
109 private:
110 sampler* sampler_ = nullptr;
111 uint64_t previous_sampling_interval_ = 0; // sampling interval before pausing
112 double previous_sampling_rate_ = 0; // sampling rate before pausing
113 int64_t previous_interval_to_next_sample_ = 0; // interval to next sample before pausing
114 };
115
123 return disable_sampling_temporarily(*this);
124 }
125
126private:
130 void reset_interval_to_next_sample(size_t alloc_size)
131 {
132 if (sampling_interval_ == 0) { // sampling is off
133 interval_to_next_sample_ = std::numeric_limits<int64_t>::max();
134 }
135 else {
136 // Large allocations we will just consider in whole. This avoids
137 // having to sample the distribution too many times if a large alloc
138 // took us very negative we just add the alloc size back on
139 if (alloc_size > sampling_interval_) {
140 interval_to_next_sample_ += alloc_size;
141 }
142 else {
143 while (interval_to_next_sample_ < 0) {
144 interval_to_next_sample_ += next_sampling_interval();
145 }
146 }
147 }
148 }
149
150 int64_t next_sampling_interval() {
151 std::exponential_distribution<double> dist(sampling_rate_);
152 int64_t next = static_cast<int64_t>(dist(random_gen));
153 // We approximate the geometric distribution using an exponential
154 // distribution.
155 return next;
156 }
157
158 uint64_t sampling_interval_; // Sample every N bytes ; 0 means off
159 double sampling_rate_; // 1 / sampling_interval_ ; used by the exp distribution
160 // How many bytes remain to be allocated before we take a sample.
161 // Specifically, if this member has value N, a sample will be taken of the allocation
162 // that allocates the Nth+1 byte.
163 int64_t interval_to_next_sample_;
164 std::random_device rd_device;
165 std::mt19937_64 random_gen;
166};
Definition: sampler.hh:42
bool maybe_sample(size_t alloc_size)
Definition: sampler.hh:67
void set_sampling_interval(uint64_t sampling_interval)
Sets the sampling interval in bytes. Setting it to 0 means to never sample.
Definition: sampler.hh:48
bool definitely_sample(size_t alloc_size)
Definition: sampler.hh:74
size_t sample_size(size_t allocation_size) const
How much should an allocation of size allocation_size count for.
Definition: sampler.hh:86
disable_sampling_temporarily pause_sampling()
Definition: sampler.hh:122
RAII class to temporarily pause sampling.
Definition: sampler.hh:91