/src/brpc/src/butil/iobuf_profiler.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #include <fcntl.h> |
19 | | #include "butil/iobuf_profiler.h" |
20 | | #include "butil/strings/string_number_conversions.h" |
21 | | #include "butil/file_util.h" |
22 | | #include "butil/fd_guard.h" |
23 | | #include "butil/fast_rand.h" |
24 | | #include "butil/hash.h" |
25 | | #include <execinfo.h> |
26 | | |
27 | | extern int BAIDU_WEAK GetStackTrace(void** result, int max_depth, int skip_count); |
28 | | |
29 | | namespace butil { |
30 | | |
31 | | namespace iobuf { |
32 | | extern void* cp(void *__restrict dest, const void *__restrict src, size_t n); |
33 | | } |
34 | | |
35 | | // Max and min sleep time for IOBuf profiler consuming thread |
36 | | // when `_sample_queue' is empty. |
37 | | const uint32_t IOBufProfiler::MIN_SLEEP_MS = 10; |
38 | | const uint32_t IOBufProfiler::MAX_SLEEP_MS = 1000; |
39 | | |
40 | | static pthread_once_t g_iobuf_profiler_info_once = PTHREAD_ONCE_INIT; |
41 | | static bool g_iobuf_profiler_enabled = false; |
42 | | static uint g_iobuf_profiler_sample_rate = 100; |
43 | | |
44 | | // Environment variables: |
45 | | // 1. ENABLE_IOBUF_PROFILER: set value to 1 to enable IOBuf profiler. |
46 | | // 2. IOBUF_PROFILER_SAMPLE_RATE: set value between (0, 100] to control sample rate. |
47 | 7 | static void InitGlobalIOBufProfilerInfo() { |
48 | 7 | const char* enabled = getenv("ENABLE_IOBUF_PROFILER"); |
49 | 7 | g_iobuf_profiler_enabled = enabled && strcmp("1", enabled) == 0 && ::GetStackTrace != NULL; |
50 | 7 | if (!g_iobuf_profiler_enabled) { |
51 | 7 | return; |
52 | 7 | } |
53 | | |
54 | 0 | char* rate = getenv("IOBUF_PROFILER_SAMPLE_RATE"); |
55 | 0 | if (rate) { |
56 | 0 | int tmp = 0; |
57 | 0 | if (butil::StringToInt(rate, &tmp)) { |
58 | 0 | if (tmp > 0 && tmp <= 100) { |
59 | 0 | g_iobuf_profiler_sample_rate = tmp; |
60 | 0 | } else { |
61 | 0 | LOG(ERROR) << "IOBUF_PROFILER_SAMPLE_RATE should be in (0, 100], but get " << rate; |
62 | 0 | } |
63 | 0 | } else { |
64 | 0 | LOG(ERROR) << "IOBUF_PROFILER_SAMPLE_RATE should be a number, but get " << rate; |
65 | 0 | } |
66 | 0 | } |
67 | 0 | LOG(INFO) << "g_iobuf_profiler_sample_rate=" << g_iobuf_profiler_sample_rate; |
68 | 0 | } |
69 | | |
70 | 26.5k | bool IsIOBufProfilerEnabled() { |
71 | 26.5k | pthread_once(&g_iobuf_profiler_info_once, InitGlobalIOBufProfilerInfo); |
72 | 26.5k | return g_iobuf_profiler_enabled; |
73 | 26.5k | } |
74 | | |
75 | 84 | bool IsIOBufProfilerSamplable() { |
76 | 84 | pthread_once(&g_iobuf_profiler_info_once, InitGlobalIOBufProfilerInfo); |
77 | 84 | if (g_iobuf_profiler_sample_rate == 100) { |
78 | 84 | return true; |
79 | 84 | } |
80 | 0 | return fast_rand_less_than(100) + 1 <= g_iobuf_profiler_sample_rate; |
81 | 84 | } |
82 | | |
83 | 0 | size_t IOBufSample::stack_hash_code() const { |
84 | 0 | if (nframes == 0) { |
85 | 0 | return 0; |
86 | 0 | } |
87 | 0 | if (_hash_code == 0) { |
88 | 0 | _hash_code = SuperFastHash(reinterpret_cast<const char*>(stack), |
89 | 0 | sizeof(void*) * nframes); |
90 | 0 | } |
91 | 0 | return _hash_code; |
92 | 0 | } |
93 | | |
94 | 0 | IOBufProfiler* IOBufProfiler::GetInstance() { |
95 | 0 | return ::Singleton<IOBufProfiler, LeakySingletonTraits<IOBufProfiler>>::get(); |
96 | 0 | } |
97 | | |
98 | | IOBufProfiler::IOBufProfiler() |
99 | 0 | : butil::SimpleThread("IOBufProfiler") |
100 | 0 | , _stop(false) |
101 | 0 | , _sleep_ms(MIN_SLEEP_MS) { |
102 | 0 | if (_stack_map.init(1024) != 0) { |
103 | 0 | LOG(WARNING) << "Fail to init _stack_map"; |
104 | 0 | } |
105 | 0 | if (_block_info_map.init(1024) != 0) { |
106 | 0 | LOG(WARNING) << "Fail to init _block_info_map"; |
107 | 0 | } |
108 | 0 | Start(); |
109 | 0 | } |
110 | | |
111 | 0 | IOBufProfiler::~IOBufProfiler() { |
112 | 0 | StopAndJoin(); |
113 | 0 | _block_info_map.clear(); |
114 | 0 | _stack_map.clear(); |
115 | | |
116 | | // Clear `_sample_queue'. |
117 | 0 | IOBufSample* sample = NULL; |
118 | 0 | while (_sample_queue.Dequeue(sample)) { |
119 | 0 | IOBufSample::Destroy(sample); |
120 | 0 | } |
121 | |
|
122 | 0 | } |
123 | | |
124 | 0 | void IOBufProfiler::Submit(IOBufSample* s) { |
125 | 0 | if (_stop.load(butil::memory_order_relaxed)) { |
126 | 0 | return; |
127 | 0 | } |
128 | | |
129 | 0 | _sample_queue.Enqueue(s); |
130 | 0 | } |
131 | | |
132 | 0 | void IOBufProfiler::Dump(IOBufSample* s) { |
133 | 0 | do { |
134 | 0 | BAIDU_SCOPED_LOCK(_mutex); |
135 | | // Categorize the stack. |
136 | 0 | IOBufRefSampleSharedPtr stack_sample; |
137 | 0 | IOBufRefSampleSharedPtr* stack_ptr = _stack_map.seek(s); |
138 | 0 | if (!stack_ptr) { |
139 | 0 | stack_sample = IOBufSample::CopyAndSharedWithDestroyer(s); |
140 | 0 | stack_sample->block = NULL; |
141 | 0 | stack_ptr = &_stack_map[stack_sample.get()]; |
142 | 0 | *stack_ptr = stack_sample; |
143 | 0 | } else { |
144 | 0 | (*stack_ptr)->count += s->count; |
145 | 0 | } |
146 | |
|
147 | 0 | BlockInfo* info = _block_info_map.seek(s->block); |
148 | 0 | if (info) { |
149 | | // Categorize the IOBufSample. |
150 | 0 | info->stack_count_map[*stack_ptr] += s->count; |
151 | 0 | info->ref += s->count; |
152 | 0 | if (info->ref == 0) { |
153 | 0 | for (const auto& iter : info->stack_count_map) { |
154 | 0 | IOBufRefSampleSharedPtr* stack_ptr2 = _stack_map.seek(iter.first.get()); |
155 | 0 | if (stack_ptr2 && *stack_ptr2) { |
156 | 0 | (*stack_ptr2)->count -= iter.second; |
157 | 0 | if ((*stack_ptr2)->count == 0) { |
158 | 0 | _stack_map.erase(iter.first.get()); |
159 | 0 | } |
160 | 0 | } |
161 | 0 | } |
162 | 0 | _block_info_map.erase(s->block); |
163 | 0 | break; |
164 | 0 | } |
165 | 0 | } else { |
166 | 0 | BlockInfo& new_info = _block_info_map[s->block]; |
167 | 0 | new_info.ref += s->count; |
168 | 0 | new_info.stack_count_map[*stack_ptr] = s->count; |
169 | 0 | } |
170 | 0 | } while (false); |
171 | 0 | s->block = NULL; |
172 | 0 | } |
173 | | |
174 | 0 | IOBufSample* IOBufSample::Copy(IOBufSample* ref) { |
175 | 0 | auto copied = IOBufSample::New(); |
176 | 0 | copied->block = ref->block; |
177 | 0 | copied->count = ref->count; |
178 | 0 | copied->_hash_code = ref->_hash_code; |
179 | 0 | copied->nframes = ref->nframes; |
180 | 0 | iobuf::cp(copied->stack, ref->stack, sizeof(void*) * ref->nframes); |
181 | 0 | return copied; |
182 | 0 | } |
183 | | |
184 | 0 | IOBufRefSampleSharedPtr IOBufSample::CopyAndSharedWithDestroyer(IOBufSample* ref) { |
185 | 0 | return { Copy(ref), detail::Destroyer() }; |
186 | 0 | } |
187 | | |
188 | 0 | void IOBufProfiler::Flush2Disk(const char* filename) { |
189 | 0 | if (!filename) { |
190 | 0 | LOG(ERROR) << "Parameter [filename] is NULL"; |
191 | 0 | return; |
192 | 0 | } |
193 | | // Serialize contentions in _stack_map into _disk_buf. |
194 | 0 | _disk_buf.append("--- contention\ncycles/second=1000000000\n"); |
195 | 0 | butil::IOBufBuilder os; |
196 | 0 | { |
197 | 0 | BAIDU_SCOPED_LOCK(_mutex); |
198 | 0 | for (auto it = _stack_map.begin(); it != _stack_map.end(); ++it) { |
199 | 0 | const IOBufRefSampleSharedPtr& c = it->second; |
200 | 0 | if (c->nframes == 0) { |
201 | 0 | LOG_EVERY_SECOND(WARNING) << "Invalid stack with nframes=0, count=" << c->count; |
202 | 0 | continue; |
203 | 0 | } |
204 | 0 | os << "0 " << c->count << " @"; |
205 | 0 | for (int i = 0; i < c->nframes; ++i) { |
206 | 0 | os << " " << c->stack[i]; |
207 | 0 | } |
208 | 0 | os << "\n"; |
209 | 0 | } |
210 | 0 | } |
211 | 0 | _disk_buf.append(os.buf().movable()); |
212 | | |
213 | | // Append /proc/self/maps to the end of the contention file, required by |
214 | | // pprof.pl, otherwise the functions in sys libs are not interpreted. |
215 | 0 | butil::IOPortal mem_maps; |
216 | 0 | const butil::fd_guard maps_fd(open("/proc/self/maps", O_RDONLY)); |
217 | 0 | if (maps_fd >= 0) { |
218 | 0 | while (true) { |
219 | 0 | ssize_t nr = mem_maps.append_from_file_descriptor(maps_fd, 8192); |
220 | 0 | if (nr < 0) { |
221 | 0 | if (errno == EINTR) { |
222 | 0 | continue; |
223 | 0 | } |
224 | 0 | PLOG(ERROR) << "Fail to read /proc/self/maps"; |
225 | 0 | break; |
226 | 0 | } |
227 | 0 | if (nr == 0) { |
228 | 0 | _disk_buf.append(mem_maps); |
229 | 0 | break; |
230 | 0 | } |
231 | 0 | } |
232 | 0 | } else { |
233 | 0 | PLOG(ERROR) << "Fail to open /proc/self/maps"; |
234 | 0 | } |
235 | | // Write _disk_buf into _filename |
236 | 0 | butil::File::Error error; |
237 | 0 | butil::FilePath file_path(filename); |
238 | 0 | butil::FilePath dir = file_path.DirName(); |
239 | 0 | if (!butil::CreateDirectoryAndGetError(dir, &error)) { |
240 | 0 | LOG(ERROR) << "Fail to create directory=`" << dir.value() |
241 | 0 | << ": " << error; |
242 | 0 | return; |
243 | 0 | } |
244 | | |
245 | 0 | butil::fd_guard fd(open(filename, O_WRONLY | O_CREAT | O_APPEND | O_TRUNC, 0666)); |
246 | 0 | if (fd < 0) { |
247 | 0 | PLOG(ERROR) << "Fail to open " << filename; |
248 | 0 | return; |
249 | 0 | } |
250 | | // Write once normally, write until empty in the end. |
251 | 0 | do { |
252 | 0 | ssize_t nw = _disk_buf.cut_into_file_descriptor(fd); |
253 | 0 | if (nw < 0) { |
254 | 0 | if (errno == EINTR) { |
255 | 0 | continue; |
256 | 0 | } |
257 | 0 | PLOG(ERROR) << "Fail to write into " << filename; |
258 | 0 | return; |
259 | 0 | } |
260 | 0 | LOG(ERROR) << "Write " << nw << " bytes into " << filename; |
261 | 0 | } while (!_disk_buf.empty()); |
262 | 0 | } |
263 | | |
264 | 0 | void IOBufProfiler::StopAndJoin() { |
265 | 0 | if (_stop.exchange(true, butil::memory_order_relaxed)) { |
266 | 0 | return; |
267 | 0 | } |
268 | 0 | if (!HasBeenJoined()) { |
269 | 0 | Join(); |
270 | 0 | } |
271 | 0 | } |
272 | | |
273 | 0 | void IOBufProfiler::Run() { |
274 | 0 | while (!_stop.load(butil::memory_order_relaxed)) { |
275 | 0 | Consume(); |
276 | 0 | ::usleep(_sleep_ms * 1000); |
277 | 0 | } |
278 | 0 | } |
279 | | |
280 | 0 | void IOBufProfiler::Consume() { |
281 | 0 | IOBufSample* sample = NULL; |
282 | 0 | bool is_empty = true; |
283 | 0 | while (_sample_queue.Dequeue(sample)) { |
284 | 0 | Dump(sample); |
285 | 0 | IOBufSample::Destroy(sample); |
286 | 0 | is_empty = false; |
287 | 0 | } |
288 | | |
289 | | // If `_sample_queue' is empty, exponential increase in sleep time. |
290 | 0 | _sleep_ms = !is_empty ? MIN_SLEEP_MS : |
291 | 0 | std::min(_sleep_ms * 2, MAX_SLEEP_MS); |
292 | 0 | } |
293 | | |
294 | 26.5k | void SubmitIOBufSample(IOBuf::Block* block, int64_t ref) { |
295 | 26.5k | if (!IsIOBufProfilerEnabled()) { |
296 | 26.5k | return; |
297 | 26.5k | } |
298 | | |
299 | 0 | auto sample = IOBufSample::New(); |
300 | 0 | sample->block = block; |
301 | 0 | sample->count = ref; |
302 | 0 | sample->nframes = GetStackTrace(sample->stack, arraysize(sample->stack), 0); |
303 | 0 | IOBufProfiler::GetInstance()->Submit(sample); |
304 | 0 | } |
305 | | |
306 | 0 | bool IOBufProfilerFlush(const char* filename) { |
307 | 0 | if (!filename) { |
308 | 0 | LOG(ERROR) << "Parameter [filename] is NULL"; |
309 | 0 | return false; |
310 | 0 | } |
311 | | |
312 | 0 | auto profiler = IOBufProfiler::GetInstance(); |
313 | 0 | profiler->Flush2Disk(filename); |
314 | 0 | return true; |
315 | 0 | } |
316 | | |
317 | | } |