Coverage Report

Created: 2024-09-03 06:23

/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
}