Commit 99728475 authored by Yinan Zhang's avatar Yinan Zhang Committed by Facebook GitHub Bot

Add a caching utility for mallctl

Summary:
This utility is meant for usage cases where
- the same `mallctl` command is called many times, and
- performance is important.

Reviewed By: yfeldblum

Differential Revision: D23638723

fbshipit-source-id: 6cab9acb1121ef4f46bf7acbe29d3877005b66b1
parent 325e3951
...@@ -25,10 +25,11 @@ namespace folly { ...@@ -25,10 +25,11 @@ namespace folly {
namespace detail { namespace detail {
[[noreturn]] void handleMallctlError(const char* cmd, int err) { [[noreturn]] void handleMallctlError(const char* fn, const char* cmd, int err) {
assert(err != 0); assert(err != 0);
cmd = cmd ? cmd : "<none>";
throw std::runtime_error( throw std::runtime_error(
sformat("mallctl {}: {} ({})", cmd, errnoStr(err), err)); sformat("mallctl[{}] {}: {} ({})", fn, cmd, errnoStr(err), err));
} }
} // namespace detail } // namespace detail
......
...@@ -18,27 +18,27 @@ ...@@ -18,27 +18,27 @@
#pragma once #pragma once
#include <folly/Likely.h>
#include <folly/memory/Malloc.h> #include <folly/memory/Malloc.h>
#include <stdexcept> #include <stdexcept>
#include <type_traits>
namespace folly { namespace folly {
namespace detail { namespace detail {
[[noreturn]] void handleMallctlError(const char* cmd, int err); [[noreturn]] void handleMallctlError(const char* fn, const char* cmd, int err);
template <typename T> template <typename T>
void mallctlHelper(const char* cmd, T* out, T* in) { void mallctlHelper(const char* cmd, T* out, T* in) {
if (UNLIKELY(!usingJEMalloc())) { if (!usingJEMalloc()) {
throw std::logic_error("Calling mallctl when not using jemalloc."); throw_exception<std::logic_error>("mallctl: not using jemalloc");
} }
size_t outLen = sizeof(T); size_t outLen = sizeof(T);
int err = mallctl(cmd, out, out ? &outLen : nullptr, in, in ? sizeof(T) : 0); int err = mallctl(cmd, out, out ? &outLen : nullptr, in, in ? sizeof(T) : 0);
if (UNLIKELY(err != 0)) { if (err != 0) {
handleMallctlError(cmd, err); handleMallctlError("mallctl", cmd, err);
} }
} }
...@@ -64,4 +64,104 @@ inline void mallctlCall(const char* cmd) { ...@@ -64,4 +64,104 @@ inline void mallctlCall(const char* cmd) {
mallctlRead<unsigned>(cmd, nullptr); mallctlRead<unsigned>(cmd, nullptr);
} }
/*
* The following implements a caching utility for usage cases where:
* - the same mallctl command is called many times, and
* - performance is important.
*/
namespace detail {
class MallctlMibCache {
protected:
explicit MallctlMibCache(const char* cmd) {
if (!usingJEMalloc()) {
throw_exception<std::logic_error>("mallctlnametomib: not using jemalloc");
}
int err = mallctlnametomib(cmd, mib, &miblen);
if (err != 0) {
handleMallctlError("mallctlnametomib", cmd, err);
}
}
template <typename ReadType, typename WriteType>
void mallctlbymibHelper(ReadType* out, WriteType* in) const {
assert((out == nullptr) == std::is_void<ReadType>::value);
assert((in == nullptr) == std::is_void<WriteType>::value);
size_t outLen = sizeofHelper<ReadType>();
int err = mallctlbymib(
mib,
miblen,
out,
out ? &outLen : nullptr,
in,
in ? sizeofHelper<WriteType>() : 0);
if (err != 0) {
handleMallctlError("mallctlbymib", nullptr, err);
}
}
private:
static constexpr size_t kMaxMibLen = 8;
size_t mib[kMaxMibLen];
size_t miblen = kMaxMibLen;
template <typename T>
constexpr size_t sizeofHelper() const {
constexpr bool v = std::is_void<T>::value;
using not_used = char;
using S = std::conditional_t<v, not_used, T>;
return v ? 0 : sizeof(S);
}
};
} // namespace detail
class MallctlMibCallCache : private detail::MallctlMibCache {
public:
explicit MallctlMibCallCache(const char* cmd) : MallctlMibCache(cmd) {}
void operator()() const {
mallctlbymibHelper((void*)nullptr, (void*)nullptr);
}
};
template <typename ReadType>
class MallctlMibReadCache : private detail::MallctlMibCache {
public:
explicit MallctlMibReadCache(const char* cmd) : MallctlMibCache(cmd) {}
ReadType operator()() const {
ReadType out;
mallctlbymibHelper(&out, (void*)nullptr);
return out;
}
};
template <typename WriteType>
class MallctlMibWriteCache : private detail::MallctlMibCache {
public:
explicit MallctlMibWriteCache(const char* cmd) : MallctlMibCache(cmd) {}
void operator()(WriteType in) const {
mallctlbymibHelper((void*)nullptr, &in);
}
};
template <typename ReadType, typename WriteType>
class MallctlMibReadWriteCache : private detail::MallctlMibCache {
public:
explicit MallctlMibReadWriteCache(const char* cmd) : MallctlMibCache(cmd) {}
ReadType operator()(WriteType in) const {
ReadType out;
mallctlbymibHelper(&out, &in);
return out;
}
};
template <typename ExchangeType>
using MallctlMibExchangeCache =
MallctlMibReadWriteCache<ExchangeType, ExchangeType>;
} // namespace folly } // namespace folly
...@@ -34,6 +34,8 @@ const char* malloc_conf = "dirty_decay_ms:10"; ...@@ -34,6 +34,8 @@ const char* malloc_conf = "dirty_decay_ms:10";
static constexpr char const* kDecayCmd = "arena.0.decay_time"; static constexpr char const* kDecayCmd = "arena.0.decay_time";
const char* malloc_conf = "purge:decay,decay_time:10"; const char* malloc_conf = "purge:decay,decay_time:10";
#endif #endif
static constexpr char const* kNoArgCmd = "arena.0.decay";
static constexpr char const* kInvalidCmd = "invalid";
class MallctlHelperTest : public ::testing::Test { class MallctlHelperTest : public ::testing::Test {
protected: protected:
...@@ -58,7 +60,7 @@ TEST_F(MallctlHelperTest, valid_read) { ...@@ -58,7 +60,7 @@ TEST_F(MallctlHelperTest, valid_read) {
TEST_F(MallctlHelperTest, invalid_read) { TEST_F(MallctlHelperTest, invalid_read) {
ssize_t decayTime = 0; ssize_t decayTime = 0;
EXPECT_THROW(mallctlRead("invalid", &decayTime), std::runtime_error); EXPECT_THROW(mallctlRead(kInvalidCmd, &decayTime), std::runtime_error);
EXPECT_EQ(0, decayTime); EXPECT_EQ(0, decayTime);
} }
...@@ -70,7 +72,7 @@ TEST_F(MallctlHelperTest, valid_write) { ...@@ -70,7 +72,7 @@ TEST_F(MallctlHelperTest, valid_write) {
TEST_F(MallctlHelperTest, invalid_write) { TEST_F(MallctlHelperTest, invalid_write) {
ssize_t decayTime = 20; ssize_t decayTime = 20;
EXPECT_THROW(mallctlWrite("invalid", decayTime), std::runtime_error); EXPECT_THROW(mallctlWrite(kInvalidCmd, decayTime), std::runtime_error);
EXPECT_EQ(10, readArena0DecayTime()); EXPECT_EQ(10, readArena0DecayTime());
} }
...@@ -86,18 +88,78 @@ TEST_F(MallctlHelperTest, invalid_read_write) { ...@@ -86,18 +88,78 @@ TEST_F(MallctlHelperTest, invalid_read_write) {
ssize_t oldDecayTime = 0; ssize_t oldDecayTime = 0;
ssize_t newDecayTime = 20; ssize_t newDecayTime = 20;
EXPECT_THROW( EXPECT_THROW(
mallctlReadWrite("invalid", &oldDecayTime, newDecayTime), mallctlReadWrite(kInvalidCmd, &oldDecayTime, newDecayTime),
std::runtime_error); std::runtime_error);
EXPECT_EQ(0, oldDecayTime); EXPECT_EQ(0, oldDecayTime);
EXPECT_EQ(10, readArena0DecayTime()); EXPECT_EQ(10, readArena0DecayTime());
} }
TEST_F(MallctlHelperTest, valid_call) { TEST_F(MallctlHelperTest, valid_call) {
EXPECT_NO_THROW(mallctlCall("arena.0.decay")); EXPECT_NO_THROW(mallctlCall(kNoArgCmd));
} }
TEST_F(MallctlHelperTest, invalid_call) { TEST_F(MallctlHelperTest, invalid_call) {
EXPECT_THROW(mallctlCall("invalid"), std::runtime_error); EXPECT_THROW(mallctlCall(kInvalidCmd), std::runtime_error);
}
TEST_F(MallctlHelperTest, read_write_cache_init) {
EXPECT_NO_THROW((MallctlMibReadWriteCache<ssize_t, ssize_t>(kDecayCmd)));
EXPECT_THROW(
(MallctlMibReadWriteCache<ssize_t, ssize_t>(kInvalidCmd)),
std::runtime_error);
EXPECT_NO_THROW((MallctlMibExchangeCache<ssize_t>(kDecayCmd)));
EXPECT_THROW(
(MallctlMibExchangeCache<ssize_t>(kInvalidCmd)), std::runtime_error);
}
TEST_F(MallctlHelperTest, read_cache_init) {
EXPECT_NO_THROW((MallctlMibReadCache<ssize_t>(kDecayCmd)));
EXPECT_THROW((MallctlMibReadCache<ssize_t>(kInvalidCmd)), std::runtime_error);
}
TEST_F(MallctlHelperTest, write_cache_init) {
EXPECT_NO_THROW((MallctlMibWriteCache<ssize_t>(kDecayCmd)));
EXPECT_THROW(
(MallctlMibWriteCache<ssize_t>(kInvalidCmd)), std::runtime_error);
}
TEST_F(MallctlHelperTest, call_cache_init) {
EXPECT_NO_THROW((MallctlMibCallCache(kNoArgCmd)));
EXPECT_THROW((MallctlMibCallCache(kInvalidCmd)), std::runtime_error);
}
TEST_F(MallctlHelperTest, valid_read_via_cache) {
MallctlMibReadCache<ssize_t> read(kDecayCmd);
ssize_t decayTime = 0;
EXPECT_NO_THROW(decayTime = read());
EXPECT_EQ(10, decayTime);
}
TEST_F(MallctlHelperTest, valid_write_via_cache) {
MallctlMibWriteCache<ssize_t> write(kDecayCmd);
ssize_t decayTime = 20;
EXPECT_NO_THROW(write(decayTime));
EXPECT_EQ(20, readArena0DecayTime());
}
TEST_F(MallctlHelperTest, valid_read_write_via_cache) {
MallctlMibReadWriteCache<ssize_t, ssize_t> read_write(kDecayCmd);
ssize_t oldDecayTime = 0;
ssize_t newDecayTime = 20;
EXPECT_NO_THROW(oldDecayTime = read_write(newDecayTime));
EXPECT_EQ(10, oldDecayTime);
EXPECT_EQ(20, readArena0DecayTime());
MallctlMibExchangeCache<ssize_t> exchange(kDecayCmd);
newDecayTime = 30;
EXPECT_NO_THROW(oldDecayTime = exchange(newDecayTime));
EXPECT_EQ(20, oldDecayTime);
EXPECT_EQ(30, readArena0DecayTime());
}
TEST_F(MallctlHelperTest, valid_call_via_cache) {
MallctlMibCallCache call(kNoArgCmd);
EXPECT_NO_THROW(call());
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment