From e25cc6ea7721acfef12fbf4547bd28ce5b63b511 Mon Sep 17 00:00:00 2001 From: Qinfan Wu Date: Sat, 23 Jul 2016 21:34:49 -0700 Subject: [PATCH] Add helper functions for mallctl Summary: As more code is using mallctl, it's worth making these helper functions available everywhere. Reviewed By: yfeldblum Differential Revision: D3576190 fbshipit-source-id: 968e80e00f2ed93542e117c24861c21745b63f20 --- folly/Makefile.am | 2 + folly/MallctlHelper.cpp | 35 ++++++++++++ folly/MallctlHelper.h | 67 ++++++++++++++++++++++ folly/detail/MemoryIdler.cpp | 45 +++++---------- folly/test/Makefile.am | 3 + folly/test/MallctlHelperTest.cpp | 96 ++++++++++++++++++++++++++++++++ 6 files changed, 218 insertions(+), 30 deletions(-) create mode 100644 folly/MallctlHelper.cpp create mode 100644 folly/MallctlHelper.h create mode 100644 folly/test/MallctlHelperTest.cpp diff --git a/folly/Makefile.am b/folly/Makefile.am index 3be134e0..fe5b0852 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -239,6 +239,7 @@ nobase_follyinclude_HEADERS = \ LockTraitsBoost.h \ Logging.h \ MacAddress.h \ + MallctlHelper.h \ Malloc.h \ MapUtil.h \ Memory.h \ @@ -362,6 +363,7 @@ libfollybase_la_SOURCES = \ EscapeTables.cpp \ Format.cpp \ FormatTables.cpp \ + MallctlHelper.cpp \ StringBase.cpp \ String.cpp \ Unicode.cpp diff --git a/folly/MallctlHelper.cpp b/folly/MallctlHelper.cpp new file mode 100644 index 00000000..7e224d08 --- /dev/null +++ b/folly/MallctlHelper.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed 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 +#include +#include + +#include + +namespace folly { + +namespace detail { + +void handleMallctlError(const char* cmd, int err) { + assert(err != 0); + throw std::runtime_error( + sformat("mallctl {}: {} ({})", cmd, errnoStr(err), err)); +} + +} // detail + +} // folly diff --git a/folly/MallctlHelper.h b/folly/MallctlHelper.h new file mode 100644 index 00000000..5c66c476 --- /dev/null +++ b/folly/MallctlHelper.h @@ -0,0 +1,67 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed 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. + */ + +// Some helper functions for mallctl. + +#pragma once + +#include +#include + +#include + +namespace folly { + +namespace detail { + +void handleMallctlError(const char* cmd, int err); + +template +void mallctlHelper(const char* cmd, T* out, T* in) { + if (UNLIKELY(!usingJEMalloc())) { + throw std::logic_error("Calling mallctl when not using jemalloc."); + } + + size_t outLen = sizeof(T); + int err = mallctl(cmd, out, out ? &outLen : nullptr, in, in ? sizeof(T) : 0); + if (UNLIKELY(err != 0)) { + handleMallctlError(cmd, err); + } +} + +} // detail + +template +void mallctlRead(const char* cmd, T* out) { + detail::mallctlHelper(cmd, out, static_cast(nullptr)); +} + +template +void mallctlWrite(const char* cmd, T in) { + detail::mallctlHelper(cmd, static_cast(nullptr), &in); +} + +template +void mallctlReadWrite(const char* cmd, T* out, T in) { + detail::mallctlHelper(cmd, out, &in); +} + +inline void mallctlCall(const char* cmd) { + // Use rather than to avoid sizeof(void). + mallctlRead(cmd, nullptr); +} + +} // folly diff --git a/folly/detail/MemoryIdler.cpp b/folly/detail/MemoryIdler.cpp index 4c63054d..6ba9a33b 100644 --- a/folly/detail/MemoryIdler.cpp +++ b/folly/detail/MemoryIdler.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -35,35 +36,16 @@ namespace folly { namespace detail { AtomicStruct MemoryIdler::defaultIdleTimeout(std::chrono::seconds(5)); - -// Calls mallctl, optionally reading a value of type if out is -// non-null. Logs on error. -template -static int mallctlRead(const char* cmd, T* out) { - size_t outLen = sizeof(T); - int err = mallctl(cmd, - out, out ? &outLen : nullptr, - nullptr, 0); - if (err != 0) { - FB_LOG_EVERY_MS(WARNING, 10000) - << "mallctl " << cmd << ": " << strerror(err) << " (" << err << ")"; - } - return err; -} - -static int mallctlCall(const char* cmd) { - // Use rather than to avoid sizeof(void). - return mallctlRead(cmd, nullptr); -} - void MemoryIdler::flushLocalMallocCaches() { - if (usingJEMalloc()) { - if (!mallctl || !mallctlnametomib || !mallctlbymib) { - FB_LOG_EVERY_MS(ERROR, 10000) << "mallctl* weak link failed"; - return; - } + if (!usingJEMalloc()) { + return; + } + if (!mallctl || !mallctlnametomib || !mallctlbymib) { + FB_LOG_EVERY_MS(ERROR, 10000) << "mallctl* weak link failed"; + return; + } - // "tcache.flush" was renamed to "thread.tcache.flush" in jemalloc 3 + try { mallctlCall("thread.tcache.flush"); // By default jemalloc has 4 arenas per cpu, and then assigns each @@ -80,13 +62,16 @@ void MemoryIdler::flushLocalMallocCaches() { unsigned arenaForCurrent; size_t mib[3]; size_t miblen = 3; - if (mallctlRead("opt.narenas", &narenas) == 0 && - narenas > 2 * CacheLocality::system().numCpus && - mallctlRead("thread.arena", &arenaForCurrent) == 0 && + + mallctlRead("opt.narenas", &narenas); + mallctlRead("thread.arena", &arenaForCurrent); + if (narenas > 2 * CacheLocality::system().numCpus && mallctlnametomib("arena.0.purge", mib, &miblen) == 0) { mib[1] = size_t(arenaForCurrent); mallctlbymib(mib, miblen, nullptr, nullptr, nullptr, 0); } + } catch (const std::runtime_error& ex) { + FB_LOG_EVERY_MS(WARNING, 10000) << ex.what(); } } diff --git a/folly/test/Makefile.am b/folly/test/Makefile.am index 84ae12a5..a7307e08 100644 --- a/folly/test/Makefile.am +++ b/folly/test/Makefile.am @@ -289,4 +289,7 @@ ssl_test_SOURCES = \ ssl_test_LDADD = libfollytestmain.la -lcrypto TESTS += ssl_test +mallctl_helper_test_SOURCES = MallctlHelperTest.cpp +TESTS += mallctl_helper_test + check_PROGRAMS += $(TESTS) diff --git a/folly/test/MallctlHelperTest.cpp b/folly/test/MallctlHelperTest.cpp new file mode 100644 index 00000000..ad665998 --- /dev/null +++ b/folly/test/MallctlHelperTest.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed 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 +#include +#include +#include + +using namespace folly; + +const char* malloc_conf = "purge:decay,decay_time:10"; + +class MallctlHelperTest : public ::testing::Test { + protected: + void TearDown() override { + // Reset decay_time of arena 0 to 10 seconds. + ssize_t decayTime = 10; + EXPECT_NO_THROW(mallctlWrite("arena.0.decay_time", decayTime)); + } + + static ssize_t readArena0DecayTime() { + ssize_t decayTime = 0; + EXPECT_NO_THROW(mallctlRead("arena.0.decay_time", &decayTime)); + return decayTime; + } +}; + +TEST_F(MallctlHelperTest, valid_read) { + ssize_t decayTime = 0; + EXPECT_NO_THROW(mallctlRead("opt.decay_time", &decayTime)); + EXPECT_EQ(10, decayTime); +} + +TEST_F(MallctlHelperTest, invalid_read) { + ssize_t decayTime = 0; + EXPECT_THROW(mallctlRead("invalid", &decayTime), std::runtime_error); + EXPECT_EQ(0, decayTime); +} + +TEST_F(MallctlHelperTest, valid_write) { + ssize_t decayTime = 20; + EXPECT_NO_THROW(mallctlWrite("arena.0.decay_time", decayTime)); + EXPECT_EQ(20, readArena0DecayTime()); +} + +TEST_F(MallctlHelperTest, invalid_write) { + ssize_t decayTime = 20; + EXPECT_THROW(mallctlWrite("invalid", decayTime), std::runtime_error); + EXPECT_EQ(10, readArena0DecayTime()); +} + +TEST_F(MallctlHelperTest, valid_read_write) { + ssize_t oldDecayTime = 0; + ssize_t newDecayTime = 20; + EXPECT_NO_THROW( + mallctlReadWrite("arena.0.decay_time", &oldDecayTime, newDecayTime)); + EXPECT_EQ(10, oldDecayTime); + EXPECT_EQ(20, readArena0DecayTime()); +} + +TEST_F(MallctlHelperTest, invalid_read_write) { + ssize_t oldDecayTime = 0; + ssize_t newDecayTime = 20; + EXPECT_THROW( + mallctlReadWrite("invalid", &oldDecayTime, newDecayTime), + std::runtime_error); + EXPECT_EQ(0, oldDecayTime); + EXPECT_EQ(10, readArena0DecayTime()); +} + +TEST_F(MallctlHelperTest, valid_call) { + EXPECT_NO_THROW(mallctlCall("arena.0.decay")); +} + +TEST_F(MallctlHelperTest, invalid_call) { + EXPECT_THROW(mallctlCall("invalid"), std::runtime_error); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + init(&argc, &argv); + return usingJEMalloc() ? RUN_ALL_TESTS() : 0; +} -- 2.34.1