blob: 8d213a72d4a608a206b4eb1e7b95aaab24de0e1e [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <fcntl.h>
#include "butil/iobuf_profiler.h"
#include "butil/strings/string_number_conversions.h"
#include "butil/file_util.h"
#include "butil/fd_guard.h"
#include "butil/fast_rand.h"
#include "butil/hash.h"
#include <execinfo.h>
extern int BAIDU_WEAK GetStackTrace(void** result, int max_depth, int skip_count);
namespace butil {
namespace iobuf {
extern void* cp(void *__restrict dest, const void *__restrict src, size_t n);
}
// Max and min sleep time for IOBuf profiler consuming thread
// when `_sample_queue' is empty.
const uint32_t IOBufProfiler::MIN_SLEEP_MS = 10;
const uint32_t IOBufProfiler::MAX_SLEEP_MS = 1000;
static pthread_once_t g_iobuf_profiler_info_once = PTHREAD_ONCE_INIT;
static bool g_iobuf_profiler_enabled = false;
static uint g_iobuf_profiler_sample_rate = 100;
// Environment variables:
// 1. ENABLE_IOBUF_PROFILER: set value to 1 to enable IOBuf profiler.
// 2. IOBUF_PROFILER_SAMPLE_RATE: set value between (0, 100] to control sample rate.
static void InitGlobalIOBufProfilerInfo() {
const char* enabled = getenv("ENABLE_IOBUF_PROFILER");
g_iobuf_profiler_enabled = enabled && strcmp("1", enabled) == 0 && ::GetStackTrace != NULL;
if (!g_iobuf_profiler_enabled) {
return;
}
char* rate = getenv("IOBUF_PROFILER_SAMPLE_RATE");
if (rate) {
int tmp = 0;
if (butil::StringToInt(rate, &tmp)) {
if (tmp > 0 && tmp <= 100) {
g_iobuf_profiler_sample_rate = tmp;
} else {
LOG(ERROR) << "IOBUF_PROFILER_SAMPLE_RATE should be in (0, 100], but get " << rate;
}
} else {
LOG(ERROR) << "IOBUF_PROFILER_SAMPLE_RATE should be a number, but get " << rate;
}
}
LOG(INFO) << "g_iobuf_profiler_sample_rate=" << g_iobuf_profiler_sample_rate;
}
bool IsIOBufProfilerEnabled() {
pthread_once(&g_iobuf_profiler_info_once, InitGlobalIOBufProfilerInfo);
return g_iobuf_profiler_enabled;
}
bool IsIOBufProfilerSamplable() {
pthread_once(&g_iobuf_profiler_info_once, InitGlobalIOBufProfilerInfo);
if (g_iobuf_profiler_sample_rate == 100) {
return true;
}
return fast_rand_less_than(100) + 1 <= g_iobuf_profiler_sample_rate;
}
size_t IOBufSample::stack_hash_code() const {
if (nframes == 0) {
return 0;
}
if (_hash_code == 0) {
_hash_code = SuperFastHash(reinterpret_cast<const char*>(stack),
sizeof(void*) * nframes);
}
return _hash_code;
}
IOBufProfiler* IOBufProfiler::GetInstance() {
return ::Singleton<IOBufProfiler, LeakySingletonTraits<IOBufProfiler>>::get();
}
IOBufProfiler::IOBufProfiler()
: butil::SimpleThread("IOBufProfiler")
, _stop(false)
, _sleep_ms(MIN_SLEEP_MS) {
if (_stack_map.init(1024) != 0) {
LOG(WARNING) << "Fail to init _stack_map";
}
if (_block_info_map.init(1024) != 0) {
LOG(WARNING) << "Fail to init _block_info_map";
}
Start();
}
IOBufProfiler::~IOBufProfiler() {
StopAndJoin();
_block_info_map.clear();
_stack_map.clear();
// Clear `_sample_queue'.
IOBufSample* sample = NULL;
while (_sample_queue.Dequeue(sample)) {
IOBufSample::Destroy(sample);
}
}
void IOBufProfiler::Submit(IOBufSample* s) {
if (_stop.load(butil::memory_order_relaxed)) {
return;
}
_sample_queue.Enqueue(s);
}
void IOBufProfiler::Dump(IOBufSample* s) {
do {
BAIDU_SCOPED_LOCK(_mutex);
// Categorize the stack.
IOBufRefSampleSharedPtr stack_sample;
IOBufRefSampleSharedPtr* stack_ptr = _stack_map.seek(s);
if (!stack_ptr) {
stack_sample = IOBufSample::CopyAndSharedWithDestroyer(s);
stack_sample->block = NULL;
stack_ptr = &_stack_map[stack_sample.get()];
*stack_ptr = stack_sample;
} else {
(*stack_ptr)->count += s->count;
}
BlockInfo* info = _block_info_map.seek(s->block);
if (info) {
// Categorize the IOBufSample.
info->stack_count_map[*stack_ptr] += s->count;
info->ref += s->count;
if (info->ref == 0) {
for (const auto& iter : info->stack_count_map) {
IOBufRefSampleSharedPtr* stack_ptr2 = _stack_map.seek(iter.first.get());
if (stack_ptr2 && *stack_ptr2) {
(*stack_ptr2)->count -= iter.second;
if ((*stack_ptr2)->count == 0) {
_stack_map.erase(iter.first.get());
}
}
}
_block_info_map.erase(s->block);
break;
}
} else {
BlockInfo& new_info = _block_info_map[s->block];
new_info.ref += s->count;
new_info.stack_count_map[*stack_ptr] = s->count;
}
} while (false);
s->block = NULL;
}
IOBufSample* IOBufSample::Copy(IOBufSample* ref) {
auto copied = IOBufSample::New();
copied->block = ref->block;
copied->count = ref->count;
copied->_hash_code = ref->_hash_code;
copied->nframes = ref->nframes;
iobuf::cp(copied->stack, ref->stack, sizeof(void*) * ref->nframes);
return copied;
}
IOBufRefSampleSharedPtr IOBufSample::CopyAndSharedWithDestroyer(IOBufSample* ref) {
return { Copy(ref), detail::Destroyer() };
}
void IOBufProfiler::Flush2Disk(const char* filename) {
if (!filename) {
LOG(ERROR) << "Parameter [filename] is NULL";
return;
}
// Serialize contentions in _stack_map into _disk_buf.
_disk_buf.append("--- contention\ncycles/second=1000000000\n");
butil::IOBufBuilder os;
{
BAIDU_SCOPED_LOCK(_mutex);
for (auto it = _stack_map.begin(); it != _stack_map.end(); ++it) {
const IOBufRefSampleSharedPtr& c = it->second;
if (c->nframes == 0) {
LOG_EVERY_SECOND(WARNING) << "Invalid stack with nframes=0, count=" << c->count;
continue;
}
os << "0 " << c->count << " @";
for (int i = 0; i < c->nframes; ++i) {
os << " " << c->stack[i];
}
os << "\n";
}
}
_disk_buf.append(os.buf().movable());
// Append /proc/self/maps to the end of the contention file, required by
// pprof.pl, otherwise the functions in sys libs are not interpreted.
butil::IOPortal mem_maps;
const butil::fd_guard maps_fd(open("/proc/self/maps", O_RDONLY));
if (maps_fd >= 0) {
while (true) {
ssize_t nr = mem_maps.append_from_file_descriptor(maps_fd, 8192);
if (nr < 0) {
if (errno == EINTR) {
continue;
}
PLOG(ERROR) << "Fail to read /proc/self/maps";
break;
}
if (nr == 0) {
_disk_buf.append(mem_maps);
break;
}
}
} else {
PLOG(ERROR) << "Fail to open /proc/self/maps";
}
// Write _disk_buf into _filename
butil::File::Error error;
butil::FilePath file_path(filename);
butil::FilePath dir = file_path.DirName();
if (!butil::CreateDirectoryAndGetError(dir, &error)) {
LOG(ERROR) << "Fail to create directory=`" << dir.value()
<< ": " << error;
return;
}
butil::fd_guard fd(open(filename, O_WRONLY | O_CREAT | O_APPEND | O_TRUNC, 0666));
if (fd < 0) {
PLOG(ERROR) << "Fail to open " << filename;
return;
}
// Write once normally, write until empty in the end.
do {
ssize_t nw = _disk_buf.cut_into_file_descriptor(fd);
if (nw < 0) {
if (errno == EINTR) {
continue;
}
PLOG(ERROR) << "Fail to write into " << filename;
return;
}
LOG(ERROR) << "Write " << nw << " bytes into " << filename;
} while (!_disk_buf.empty());
}
void IOBufProfiler::StopAndJoin() {
if (_stop.exchange(true, butil::memory_order_relaxed)) {
return;
}
if (!HasBeenJoined()) {
Join();
}
}
void IOBufProfiler::Run() {
while (!_stop.load(butil::memory_order_relaxed)) {
Consume();
::usleep(_sleep_ms * 1000);
}
}
void IOBufProfiler::Consume() {
IOBufSample* sample = NULL;
bool is_empty = true;
while (_sample_queue.Dequeue(sample)) {
Dump(sample);
IOBufSample::Destroy(sample);
is_empty = false;
}
// If `_sample_queue' is empty, exponential increase in sleep time.
_sleep_ms = !is_empty ? MIN_SLEEP_MS :
std::min(_sleep_ms * 2, MAX_SLEEP_MS);
}
void SubmitIOBufSample(IOBuf::Block* block, int64_t ref) {
if (!IsIOBufProfilerEnabled()) {
return;
}
auto sample = IOBufSample::New();
sample->block = block;
sample->count = ref;
sample->nframes = GetStackTrace(sample->stack, arraysize(sample->stack), 0);
IOBufProfiler::GetInstance()->Submit(sample);
}
bool IOBufProfilerFlush(const char* filename) {
if (!filename) {
LOG(ERROR) << "Parameter [filename] is NULL";
return false;
}
auto profiler = IOBufProfiler::GetInstance();
profiler->Flush2Disk(filename);
return true;
}
}