Add helper functions for mallctl
authorQinfan Wu <wqfish@fb.com>
Sun, 24 Jul 2016 04:34:49 +0000 (21:34 -0700)
committerFacebook Github Bot 8 <facebook-github-bot-8-bot@fb.com>
Sun, 24 Jul 2016 04:38:42 +0000 (21:38 -0700)
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
folly/MallctlHelper.cpp [new file with mode: 0644]
folly/MallctlHelper.h [new file with mode: 0644]
folly/detail/MemoryIdler.cpp
folly/test/Makefile.am
folly/test/MallctlHelperTest.cpp [new file with mode: 0644]

index 3be134e04d9306a8f143cc10ce02c4b3ba4cb0a9..fe5b0852f42a51c686835e4a4b09234bb4250606 100644 (file)
@@ -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 (file)
index 0000000..7e224d0
--- /dev/null
@@ -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 <folly/MallctlHelper.h>
+#include <folly/Format.h>
+#include <folly/String.h>
+
+#include <stdexcept>
+
+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 (file)
index 0000000..5c66c47
--- /dev/null
@@ -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 <folly/Likely.h>
+#include <folly/Malloc.h>
+
+#include <stdexcept>
+
+namespace folly {
+
+namespace detail {
+
+void handleMallctlError(const char* cmd, int err);
+
+template <typename T>
+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 <typename T>
+void mallctlRead(const char* cmd, T* out) {
+  detail::mallctlHelper(cmd, out, static_cast<T*>(nullptr));
+}
+
+template <typename T>
+void mallctlWrite(const char* cmd, T in) {
+  detail::mallctlHelper(cmd, static_cast<T*>(nullptr), &in);
+}
+
+template <typename T>
+void mallctlReadWrite(const char* cmd, T* out, T in) {
+  detail::mallctlHelper(cmd, out, &in);
+}
+
+inline void mallctlCall(const char* cmd) {
+  // Use <unsigned> rather than <void> to avoid sizeof(void).
+  mallctlRead<unsigned>(cmd, nullptr);
+}
+
+} // folly
index 4c63054d442623d77175b5cadbf0167ffba2d668..6ba9a33baafe613b41ee997a2a7ce97a061b7f4c 100644 (file)
@@ -17,6 +17,7 @@
 #include <folly/detail/MemoryIdler.h>
 
 #include <folly/Logging.h>
+#include <folly/MallctlHelper.h>
 #include <folly/Malloc.h>
 #include <folly/Portability.h>
 #include <folly/ScopeGuard.h>
@@ -35,35 +36,16 @@ namespace folly { namespace detail {
 AtomicStruct<std::chrono::steady_clock::duration>
 MemoryIdler::defaultIdleTimeout(std::chrono::seconds(5));
 
-
-// Calls mallctl, optionally reading a value of type <T> if out is
-// non-null.  Logs on error.
-template <typename T>
-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 <unsigned> rather than <void> to avoid sizeof(void).
-  return mallctlRead<unsigned>(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<unsigned>("opt.narenas", &narenas) == 0 &&
-        narenas > 2 * CacheLocality::system().numCpus &&
-        mallctlRead<unsigned>("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();
   }
 }
 
index 84ae12a53be1b7f1150b0b313927564593df91a8..a7307e08a2e02ff5c3e229f268614afd1c699ccb 100644 (file)
@@ -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 (file)
index 0000000..ad66599
--- /dev/null
@@ -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 <folly/MallctlHelper.h>
+#include <folly/Malloc.h>
+#include <folly/init/Init.h>
+#include <gtest/gtest.h>
+
+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;
+}