Huge page library.
authorTudor Bosman <tudorb@fb.com>
Wed, 22 Aug 2012 00:16:02 +0000 (17:16 -0700)
committerJordan DeLong <jdelong@fb.com>
Mon, 17 Sep 2012 01:28:00 +0000 (18:28 -0700)
Test Plan: by hand

Reviewed By: lucian@fb.com

FB internal diff: D555164

folly/experimental/io/HugePageUtil.cpp [new file with mode: 0644]
folly/experimental/io/HugePages.cpp [new file with mode: 0644]
folly/experimental/io/HugePages.h [new file with mode: 0644]

diff --git a/folly/experimental/io/HugePageUtil.cpp b/folly/experimental/io/HugePageUtil.cpp
new file mode 100644 (file)
index 0000000..1506adb
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012 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 <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <stdexcept>
+#include <system_error>
+
+#include <gflags/gflags.h>
+
+#include "folly/experimental/io/HugePages.h"
+#include "folly/Format.h"
+#include "folly/Range.h"
+#include "folly/ScopeGuard.h"
+
+DEFINE_bool(cp, false, "Copy file");
+
+using namespace folly;
+
+namespace {
+
+void usage(const char* name) __attribute__((noreturn));
+
+void usage(const char* name) {
+  std::cerr << folly::format(
+      "Usage: {0}\n"
+      "         list all huge page sizes and their mount points\n"
+      "       {0} -cp <src_file> <dest_nameprefix>\n"
+      "         copy src_file to a huge page file\n",
+      name);
+  exit(1);
+}
+
+void copy(const char* srcFile, const char* destPrefix) {
+  int srcfd = open(srcFile, O_RDONLY);
+  if (srcfd == -1) {
+    throw std::system_error(errno, std::system_category(), "open failed");
+  }
+  SCOPE_EXIT {
+    close(srcfd);
+  };
+  struct stat st;
+  if (fstat(srcfd, &st) == -1) {
+    throw std::system_error(errno, std::system_category(), "fstat failed");
+  }
+
+  void* start = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, srcfd, 0);
+  if (start == MAP_FAILED) {
+    throw std::system_error(errno, std::system_category(), "mmap failed");
+  }
+
+  SCOPE_EXIT {
+    munmap(start, st.st_size);
+  };
+
+  HugePages hp;
+  auto f = hp.create(ByteRange(static_cast<const unsigned char*>(start),
+                               st.st_size),
+                     destPrefix);
+  std::cout << f.path << "\n";
+}
+
+void list() {
+  HugePages hp;
+  for (auto& p : hp.sizes()) {
+    std::cout << p.first << " " << p.second << "\n";
+  }
+}
+
+}  // namespace
+
+
+int main(int argc, char *argv[]) {
+  google::ParseCommandLineFlags(&argc, &argv, true);
+  if (FLAGS_cp) {
+    if (argc != 3) usage(argv[0]);
+    copy(argv[1], argv[2]);
+  } else {
+    if (argc != 1) usage(argv[0]);
+    list();
+  }
+  return 0;
+}
+
diff --git a/folly/experimental/io/HugePages.cpp b/folly/experimental/io/HugePages.cpp
new file mode 100644 (file)
index 0000000..3591410
--- /dev/null
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2012 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/experimental/io/HugePages.h"
+
+#include <sys/mman.h>
+
+#include <cctype>
+#include <cstring>
+
+#include <algorithm>
+#include <stdexcept>
+#include <system_error>
+
+#include <boost/filesystem.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/regex.hpp>
+
+#include <glog/logging.h>
+
+#include "folly/Conv.h"
+#include "folly/Format.h"
+#include "folly/Range.h"
+#include "folly/ScopeGuard.h"
+#include "folly/String.h"
+#include "folly/experimental/io/Stream.h"
+
+namespace fs = ::boost::filesystem;
+
+namespace folly {
+
+namespace {
+
+// Get the default huge page size
+size_t getDefaultHugePageSize() {
+  // We need to parse /proc/meminfo
+  static const boost::regex regex(R"!(Hugepagesize:\s*(\d+)\s*kB)!");
+  boost::cmatch match;
+  for (auto& byteLine : byLine("/proc/meminfo")) {
+    StringPiece line(byteLine);
+    if (boost::regex_match(line.begin(), line.end(), match, regex)) {
+      StringPiece numStr(line.begin() + match.position(1), match.length(1));
+      return to<size_t>(numStr) * 1024;  // in KiB
+    }
+  }
+  throw std::runtime_error("Can't find default huge page size");
+}
+
+// Get raw huge page sizes (without mount points, they'll be filled later)
+HugePageSizeVec getRawHugePageSizes() {
+  // We need to parse file names from /sys/kernel/mm/hugepages
+  static const boost::regex regex(R"!(hugepages-(\d+)kB)!");
+  boost::smatch match;
+  HugePageSizeVec vec;
+  fs::path path("/sys/kernel/mm/hugepages");
+  for (fs::directory_iterator it(path); it != fs::directory_iterator(); ++it) {
+    std::string filename(it->path().filename().native());
+    if (boost::regex_match(filename, match, regex)) {
+      StringPiece numStr(filename.data() + match.position(1), match.length(1));
+      vec.emplace_back(to<size_t>(numStr) * 1024, "");
+    }
+  }
+  return vec;
+}
+
+// Parse the value of a pagesize mount option
+// Format: number, optional K/M/G/T suffix, trailing junk allowed
+size_t parsePageSizeValue(StringPiece value) {
+  static const boost::regex regex(R"!((\d+)([kmgt])?.*)!", boost::regex::icase);
+  boost::cmatch match;
+  if (!boost::regex_match(value.begin(), value.end(), match, regex)) {
+    throw std::runtime_error("Invalid pagesize option");
+  }
+  char c = '\0';
+  if (match.length(2) != 0) {
+    c = tolower(value[match.position(2)]);
+  }
+  StringPiece numStr(value.data() + match.position(1), match.length(1));
+  size_t size = to<size_t>(numStr);
+  switch (c) {
+  case 't': size *= 1024;
+  case 'g': size *= 1024;
+  case 'm': size *= 1024;
+  case 'k': size *= 1024;
+  }
+  return size;
+}
+
+/**
+ * Get list of supported huge page sizes and their mount points, if
+ * hugetlbfs file systems are mounted for those sizes.
+ */
+HugePageSizeVec getHugePageSizes() {
+  HugePageSizeVec sizeVec = getRawHugePageSizes();
+  if (sizeVec.empty()) {
+    return sizeVec;  // nothing to do
+  }
+  std::sort(sizeVec.begin(), sizeVec.end());
+
+  size_t defaultHugePageSize = getDefaultHugePageSize();
+
+  struct PageSizeLess {
+    bool operator()(const std::pair<size_t, std::string>& a, size_t b) const {
+      return a.first < b;
+    }
+    bool operator()(size_t a, const std::pair<size_t, std::string>& b) const {
+      return a < b.first;
+    }
+  };
+
+  // Read and parse /proc/mounts
+  std::vector<StringPiece> parts;
+  std::vector<StringPiece> options;
+  for (auto& byteLine : byLine("/proc/mounts")) {
+    StringPiece line(byteLine);
+    parts.clear();
+    split(" ", line, parts);
+    // device path fstype options uid gid
+    if (parts.size() != 6) {
+      throw std::runtime_error("Invalid /proc/mounts line");
+    }
+    if (parts[2] != "hugetlbfs") {
+      continue;  // we only care about hugetlbfs
+    }
+
+    options.clear();
+    split(",", parts[3], options);
+    size_t pageSize = defaultHugePageSize;
+    // Search for the "pagesize" option, which must have a value
+    for (auto& option : options) {
+      // key=value
+      const char* p = static_cast<const char*>(
+          memchr(option.data(), '=', option.size()));
+      if (!p) {
+        continue;
+      }
+      if (StringPiece(option.data(), p) != "pagesize") {
+        continue;
+      }
+      pageSize = parsePageSizeValue(StringPiece(p + 1, option.end()));
+      break;
+    }
+
+    auto pos = std::lower_bound(sizeVec.begin(), sizeVec.end(), pageSize,
+                                PageSizeLess());
+    if (pos == sizeVec.end() || pos->first != pageSize) {
+      throw std::runtime_error("Mount page size not found");
+    }
+    if (pos->second.empty()) {
+      // Store mount point
+      pos->second.assign(parts[1].data(), parts[1].size());
+    }
+  }
+
+  return sizeVec;
+}
+
+// RAII wrapper around an open file, closes on exit unless you call release()
+class ScopedFd : private boost::noncopyable {
+ public:
+  explicit ScopedFd(int fd) : fd_(fd) { }
+  int fd() const { return fd_; }
+
+  void release() {
+    fd_ = -1;
+  }
+
+  void close() {
+    if (fd_ == -1) {
+      return;
+    }
+    int r = ::close(fd_);
+    fd_ = -1;
+    if (r == -1) {
+      throw std::system_error(errno, std::system_category(), "close failed");
+    }
+  }
+
+  ~ScopedFd() {
+    try {
+      close();
+    } catch (...) {
+      PLOG(ERROR) << "close failed!";
+    }
+  }
+
+ private:
+  int fd_;
+};
+
+// RAII wrapper that deletes a file upon destruction unless you call release()
+class ScopedDeleter : private boost::noncopyable {
+ public:
+  explicit ScopedDeleter(std::string name) : name_(std::move(name)) { }
+  void release() {
+    name_.clear();
+  }
+
+  ~ScopedDeleter() {
+    if (name_.empty()) {
+      return;
+    }
+    int r = ::unlink(name_.c_str());
+    if (r == -1) {
+      PLOG(ERROR) << "unlink failed";
+    }
+  }
+ private:
+  std::string name_;
+};
+
+// RAII wrapper around a mmap mapping, munmaps upon destruction unless you
+// call release()
+class ScopedMmap : private boost::noncopyable {
+ public:
+  ScopedMmap(void* start, size_t size) : start_(start), size_(size) { }
+
+  void* start() const { return start_; }
+  size_t size() const { return size_; }
+
+  void release() {
+    start_ = MAP_FAILED;
+  }
+
+  void munmap() {
+    if (start_ == MAP_FAILED) {
+      return;
+    }
+    int r = ::munmap(start_, size_);
+    start_ = MAP_FAILED;
+    if (r == -1) {
+      throw std::system_error(errno, std::system_category(), "munmap failed");
+    }
+  }
+
+  ~ScopedMmap() {
+    try {
+      munmap();
+    } catch (...) {
+      PLOG(ERROR) << "munmap failed!";
+    }
+  }
+ private:
+  void* start_;
+  size_t size_;
+};
+
+}  // namespace
+
+HugePages::HugePages() : sizes_(getHugePageSizes()) { }
+
+HugePages::File HugePages::create(ByteRange data,
+                                  StringPiece baseName,
+                                  size_t hugePageSize,
+                                  mode_t mode) const {
+  // Pick an appropriate size.
+  StringPiece mountPath;
+  if (hugePageSize == 0) {
+    for (auto& p : sizes_) {
+      if (p.second.empty()) {
+        continue;  // not mounted
+      }
+      hugePageSize = p.first;
+      mountPath = StringPiece(p.second);
+      break;
+    }
+    if (hugePageSize == 0) {
+      throw std::runtime_error("No huge page filesystem mounted");
+    }
+  } else {
+    // Linear search is just fine
+    for (auto& p : sizes_) {
+      if (p.first == hugePageSize) {
+        if (p.second.empty()) {
+          throw std::runtime_error(
+              "No huge page filesystem mounted with requested page size");
+        }
+        mountPath = StringPiece(p.second);
+      }
+    }
+    if (mountPath.empty()) {
+      throw std::runtime_error("Requested huge page size not found");
+    }
+  }
+
+  // Round size up
+  File file;
+  file.size = data.size() / hugePageSize * hugePageSize;
+  if (file.size != data.size()) {
+    file.size += hugePageSize;
+  }
+
+  file.path = folly::format("{}/{}", mountPath, baseName).str();
+  ScopedFd fd(open(file.path.c_str(), O_RDWR | O_CREAT | O_TRUNC, mode));
+  if (fd.fd() == -1) {
+    throw std::system_error(errno, std::system_category(), "open failed");
+  }
+
+  ScopedDeleter deleter(file.path);
+
+  ScopedMmap map(mmap(nullptr, file.size, PROT_READ | PROT_WRITE, MAP_SHARED,
+                      fd.fd(), 0),
+                 file.size);
+  if (map.start() == MAP_FAILED) {
+    throw std::system_error(errno, std::system_category(), "mmap failed");
+  }
+
+  memcpy(map.start(), data.data(), data.size());
+
+  map.munmap();
+  deleter.release();
+  fd.close();
+
+  return file;
+}
+
+}  // namespace folly
+
diff --git a/folly/experimental/io/HugePages.h b/folly/experimental/io/HugePages.h
new file mode 100644 (file)
index 0000000..6ec6a1f
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012 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.
+ */
+
+#ifndef FOLLY_IO_HUGEPAGES_H_
+#define FOLLY_IO_HUGEPAGES_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include <cstddef>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "folly/Range.h"
+
+namespace folly {
+
+/**
+ * Vector of (huge_page_size, mount_point), sorted by huge_page_size.
+ * mount_point might be empty if no hugetlbfs file system is mounted for
+ * that size.
+ */
+typedef std::vector<std::pair<size_t, std::string>> HugePageSizeVec;
+
+/**
+ * Class to interface with Linux huge pages (hugetlbfs).
+ */
+class HugePages {
+ public:
+  HugePages();
+
+  /**
+   * Get list of supported huge page sizes and their mount points, if
+   * hugetlbfs file systems are mounted for those sizes.
+   */
+  const HugePageSizeVec& sizes() const { return sizes_; }
+
+  /**
+   * Create a file on a huge page filesystem containing a copy of the data
+   * from data.  If multiple huge page sizes are allowed, we
+   * pick the smallest huge page size available, unless you request one
+   * explicitly with the hugePageSize argument.
+   *
+   * We return a struct File structure containing the full path and size
+   * (rounded up to a multiple of the huge page size)
+   */
+  struct File {
+    std::string path;
+    size_t size;
+  };
+  File create(
+      ByteRange data, StringPiece baseName, size_t hugePageSize = 0,
+      mode_t mode = 0644) const;
+
+ private:
+  HugePageSizeVec sizes_;
+};
+
+}  // namespace folly
+
+#endif /* FOLLY_IO_HUGEPAGES_H_ */
+