/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 | 19.9k | bool IsIOBufProfilerEnabled() { |
71 | 19.9k | pthread_once(&g_iobuf_profiler_info_once, InitGlobalIOBufProfilerInfo); |
72 | 19.9k | return g_iobuf_profiler_enabled; |
73 | 19.9k | } |
74 | | |
75 | 75 | bool IsIOBufProfilerSamplable() { |
76 | 75 | pthread_once(&g_iobuf_profiler_info_once, InitGlobalIOBufProfilerInfo); |
77 | 75 | if (g_iobuf_profiler_sample_rate == 100) { |
78 | 75 | return true; |
79 | 75 | } |
80 | 0 | return fast_rand_less_than(100) + 1 <= g_iobuf_profiler_sample_rate; |
81 | 75 | } |
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 | | : butil::SimpleThread("IOBufProfiler") |
100 | | , _stop(false) |
101 | 0 | , _sleep_ms(MIN_SLEEP_MS) { |
102 | 0 | CHECK_EQ(0, _stack_map.init(1024)); |
103 | 0 | CHECK_EQ(0, _block_info_map.init(1024)); |
104 | 0 | Start(); |
105 | 0 | } |
106 | | |
107 | 0 | IOBufProfiler::~IOBufProfiler() { |
108 | 0 | StopAndJoin(); |
109 | 0 | _block_info_map.clear(); |
110 | 0 | _stack_map.clear(); |
111 | | |
112 | | // Clear `_sample_queue'. |
113 | 0 | IOBufSample* sample = NULL; |
114 | 0 | while (_sample_queue.Dequeue(sample)) { |
115 | 0 | IOBufSample::Destroy(sample); |
116 | 0 | } |
117 | |
|
118 | 0 | } |
119 | | |
120 | 0 | void IOBufProfiler::Submit(IOBufSample* s) { |
121 | 0 | if (_stop.load(butil::memory_order_relaxed)) { |
122 | 0 | return; |
123 | 0 | } |
124 | | |
125 | 0 | _sample_queue.Enqueue(s); |
126 | 0 | } |
127 | | |
128 | 0 | void IOBufProfiler::Dump(IOBufSample* s) { |
129 | 0 | do { |
130 | 0 | BAIDU_SCOPED_LOCK(_mutex); |
131 | | // Categorize the stack. |
132 | 0 | IOBufRefSampleSharedPtr stack_sample; |
133 | 0 | IOBufRefSampleSharedPtr* stack_ptr = _stack_map.seek(s); |
134 | 0 | if (!stack_ptr) { |
135 | 0 | stack_sample = IOBufSample::CopyAndSharedWithDestroyer(s); |
136 | 0 | stack_sample->block = NULL; |
137 | 0 | stack_ptr = &_stack_map[stack_sample.get()]; |
138 | 0 | *stack_ptr = stack_sample; |
139 | 0 | } else { |
140 | 0 | (*stack_ptr)->count += s->count; |
141 | 0 | } |
142 | |
|
143 | 0 | BlockInfo* info = _block_info_map.seek(s->block); |
144 | 0 | if (info) { |
145 | | // Categorize the IOBufSample. |
146 | 0 | info->stack_count_map[*stack_ptr] += s->count; |
147 | 0 | info->ref += s->count; |
148 | 0 | if (info->ref == 0) { |
149 | 0 | for (const auto& iter : info->stack_count_map) { |
150 | 0 | IOBufRefSampleSharedPtr* stack_ptr2 = _stack_map.seek(iter.first.get()); |
151 | 0 | if (stack_ptr2 && *stack_ptr2) { |
152 | 0 | (*stack_ptr2)->count -= iter.second; |
153 | 0 | if ((*stack_ptr2)->count == 0) { |
154 | 0 | _stack_map.erase(iter.first.get()); |
155 | 0 | } |
156 | 0 | } |
157 | 0 | } |
158 | 0 | _block_info_map.erase(s->block); |
159 | 0 | break; |
160 | 0 | } |
161 | 0 | } else { |
162 | 0 | BlockInfo& new_info = _block_info_map[s->block]; |
163 | 0 | new_info.ref += s->count; |
164 | 0 | CHECK_EQ(0, new_info.stack_count_map.init(64)); |
165 | 0 | new_info.stack_count_map[*stack_ptr] = s->count; |
166 | 0 | } |
167 | 0 | } while (false); |
168 | 0 | s->block = NULL; |
169 | 0 | } |
170 | | |
171 | 0 | IOBufSample* IOBufSample::Copy(IOBufSample* ref) { |
172 | 0 | auto copied = IOBufSample::New(); |
173 | 0 | copied->block = ref->block; |
174 | 0 | copied->count = ref->count; |
175 | 0 | copied->_hash_code = ref->_hash_code; |
176 | 0 | copied->nframes = ref->nframes; |
177 | 0 | iobuf::cp(copied->stack, ref->stack, sizeof(void*) * ref->nframes); |
178 | 0 | return copied; |
179 | 0 | } |
180 | | |
181 | 0 | IOBufRefSampleSharedPtr IOBufSample::CopyAndSharedWithDestroyer(IOBufSample* ref) { |
182 | 0 | return { Copy(ref), detail::Destroyer() }; |
183 | 0 | } |
184 | | |
185 | 0 | void IOBufProfiler::Flush2Disk(const char* filename) { |
186 | 0 | if (!filename) { |
187 | 0 | LOG(ERROR) << "Parameter [filename] is NULL"; |
188 | 0 | return; |
189 | 0 | } |
190 | | // Serialize contentions in _stack_map into _disk_buf. |
191 | 0 | _disk_buf.append("--- contention\ncycles/second=1000000000\n"); |
192 | 0 | butil::IOBufBuilder os; |
193 | 0 | { |
194 | 0 | BAIDU_SCOPED_LOCK(_mutex); |
195 | 0 | for (auto it = _stack_map.begin(); it != _stack_map.end(); ++it) { |
196 | 0 | const IOBufRefSampleSharedPtr& c = it->second; |
197 | 0 | if (c->nframes == 0) { |
198 | 0 | LOG_EVERY_SECOND(WARNING) << "Invalid stack with nframes=0, count=" << c->count; |
199 | 0 | continue; |
200 | 0 | } |
201 | 0 | os << "0 " << c->count << " @"; |
202 | 0 | for (int i = 0; i < c->nframes; ++i) { |
203 | 0 | os << " " << c->stack[i]; |
204 | 0 | } |
205 | 0 | os << "\n"; |
206 | 0 | } |
207 | 0 | } |
208 | 0 | _disk_buf.append(os.buf().movable()); |
209 | | |
210 | | // Append /proc/self/maps to the end of the contention file, required by |
211 | | // pprof.pl, otherwise the functions in sys libs are not interpreted. |
212 | 0 | butil::IOPortal mem_maps; |
213 | 0 | const butil::fd_guard maps_fd(open("/proc/self/maps", O_RDONLY)); |
214 | 0 | if (maps_fd >= 0) { |
215 | 0 | while (true) { |
216 | 0 | ssize_t nr = mem_maps.append_from_file_descriptor(maps_fd, 8192); |
217 | 0 | if (nr < 0) { |
218 | 0 | if (errno == EINTR) { |
219 | 0 | continue; |
220 | 0 | } |
221 | 0 | PLOG(ERROR) << "Fail to read /proc/self/maps"; |
222 | 0 | break; |
223 | 0 | } |
224 | 0 | if (nr == 0) { |
225 | 0 | _disk_buf.append(mem_maps); |
226 | 0 | break; |
227 | 0 | } |
228 | 0 | } |
229 | 0 | } else { |
230 | 0 | PLOG(ERROR) << "Fail to open /proc/self/maps"; |
231 | 0 | } |
232 | | // Write _disk_buf into _filename |
233 | 0 | butil::File::Error error; |
234 | 0 | butil::FilePath file_path(filename); |
235 | 0 | butil::FilePath dir = file_path.DirName(); |
236 | 0 | if (!butil::CreateDirectoryAndGetError(dir, &error)) { |
237 | 0 | LOG(ERROR) << "Fail to create directory=`" << dir.value() |
238 | 0 | << ": " << error; |
239 | 0 | return; |
240 | 0 | } |
241 | | |
242 | 0 | butil::fd_guard fd(open(filename, O_WRONLY | O_CREAT | O_APPEND | O_TRUNC, 0666)); |
243 | 0 | if (fd < 0) { |
244 | 0 | PLOG(ERROR) << "Fail to open " << filename; |
245 | 0 | return; |
246 | 0 | } |
247 | | // Write once normally, write until empty in the end. |
248 | 0 | do { |
249 | 0 | ssize_t nw = _disk_buf.cut_into_file_descriptor(fd); |
250 | 0 | if (nw < 0) { |
251 | 0 | if (errno == EINTR) { |
252 | 0 | continue; |
253 | 0 | } |
254 | 0 | PLOG(ERROR) << "Fail to write into " << filename; |
255 | 0 | return; |
256 | 0 | } |
257 | 0 | LOG(ERROR) << "Write " << nw << " bytes into " << filename; |
258 | 0 | } while (!_disk_buf.empty()); |
259 | 0 | } |
260 | | |
261 | 0 | void IOBufProfiler::StopAndJoin() { |
262 | 0 | if (_stop.exchange(true, butil::memory_order_relaxed)) { |
263 | 0 | return; |
264 | 0 | } |
265 | 0 | if (!HasBeenJoined()) { |
266 | 0 | Join(); |
267 | 0 | } |
268 | 0 | } |
269 | | |
270 | 0 | void IOBufProfiler::Run() { |
271 | 0 | while (!_stop.load(butil::memory_order_relaxed)) { |
272 | 0 | Consume(); |
273 | 0 | ::usleep(_sleep_ms * 1000); |
274 | 0 | } |
275 | 0 | } |
276 | | |
277 | 0 | void IOBufProfiler::Consume() { |
278 | 0 | IOBufSample* sample = NULL; |
279 | 0 | bool is_empty = true; |
280 | 0 | while (_sample_queue.Dequeue(sample)) { |
281 | 0 | Dump(sample); |
282 | 0 | IOBufSample::Destroy(sample); |
283 | 0 | is_empty = false; |
284 | 0 | } |
285 | | |
286 | | // If `_sample_queue' is empty, exponential increase in sleep time. |
287 | 0 | _sleep_ms = !is_empty ? MIN_SLEEP_MS : |
288 | 0 | std::min(_sleep_ms * 2, MAX_SLEEP_MS); |
289 | 0 | } |
290 | | |
291 | 19.9k | void SubmitIOBufSample(IOBuf::Block* block, int64_t ref) { |
292 | 19.9k | if (!IsIOBufProfilerEnabled()) { |
293 | 19.9k | return; |
294 | 19.9k | } |
295 | | |
296 | 0 | auto sample = IOBufSample::New(); |
297 | 0 | sample->block = block; |
298 | 0 | sample->count = ref; |
299 | 0 | sample->nframes = GetStackTrace(sample->stack, arraysize(sample->stack), 0); |
300 | 0 | IOBufProfiler::GetInstance()->Submit(sample); |
301 | 0 | } |
302 | | |
303 | 0 | bool IOBufProfilerFlush(const char* filename) { |
304 | 0 | if (!filename) { |
305 | 0 | LOG(ERROR) << "Parameter [filename] is NULL"; |
306 | 0 | return false; |
307 | 0 | } |
308 | | |
309 | 0 | auto profiler = IOBufProfiler::GetInstance(); |
310 | 0 | profiler->Flush2Disk(filename); |
311 | 0 | return true; |
312 | 0 | } |
313 | | |
314 | | } |