cachelib/common/Utils.cpp (316 lines of code) (raw):

/* * Copyright (c) Facebook, Inc. and its affiliates. * * 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 <dirent.h> #include <folly/experimental/exception_tracer/ExceptionTracer.h> #include <sys/mman.h> #include <sys/resource.h> #include <sys/shm.h> #include <sys/stat.h> #include <sys/types.h> #include <fstream> #include <iostream> #include <stdexcept> #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #include <folly/Format.h> #pragma GCC diagnostic pop #include <folly/Random.h> #include <folly/logging/xlog.h> #include "cachelib/common/Utils.h" namespace facebook { namespace cachelib { namespace util { namespace { constexpr size_t kPageSizeBytes = 4096; const char* kProcShmMaxPath = "/proc/sys/kernel/shmmax"; const char* kProcShmAllPath = "/proc/sys/kernel/shmall"; // Get the value of kernel.shmmax in bytes. // This function must be run as root. // // Use this to determine how much shm we can allocate on this system, // if it's too low, the user may want to use `setShmMax` to set a higher // limit. uint64_t getShmMax() { std::ifstream shmMaxFile(kProcShmMaxPath); if (!shmMaxFile) { return 0; } uint64_t shmMax; if (shmMaxFile >> shmMax) { return shmMax; } return 0; } // Set the value of kernel.shmmax. This function must be run as root. // // @param shmMax new shmmax value in bytes // // @return true on success, false otherwise bool setShmMax(uint64_t shmMax) { std::ofstream shmMaxFile(kProcShmMaxPath, std::ios_base::trunc); if (!shmMaxFile) { return false; } shmMaxFile << shmMax; shmMaxFile.flush(); return shmMaxFile.good(); } // Get the value of kernel.shmall in number of pages. // This function must be run as root. // // Use this to determine how much shm we can allocate *in total* on this system, // if it's too low, the user may want to use `setShmAll` to set a higher // limit. uint64_t getShmAll() { std::ifstream shmAllFile(kProcShmAllPath); if (!shmAllFile) { return 0; } uint64_t shmAll; if (shmAllFile >> shmAll) { return shmAll; } return 0; } // Set the value of kernel.shmall. This function must be run as root. // // "shmall" is suggested to be set to "shmmax / PAGE_SIZE" // // @param shmAll new shmmax value in bytes // // @return true on success, false otherwise bool setShmAll(uint64_t shmAll) { std::ofstream shmAllFile(kProcShmAllPath, std::ios_base::trunc); if (!shmAllFile) { return false; } shmAllFile << shmAll; shmAllFile.flush(); return shmAllFile.good(); } } // namespace void setShmIfNecessary(uint64_t bytes) { const auto curShmMax = getShmMax(); if (curShmMax < bytes && !setShmMax(bytes)) { throw std::system_error( ENOMEM, std::system_category(), folly::sformat("Cannot set shmmax to {} from {}", bytes, curShmMax)); } const auto curShmAll = getShmAll(); if (curShmAll * kPageSizeBytes < bytes) { // always set this to be just bigger than shmmax const auto desiredNumPages = bytes / kPageSizeBytes + 1; if (!setShmAll(desiredNumPages)) { throw std::system_error(ENOMEM, std::system_category(), folly::sformat("Cannot set shmall to {} from {}", desiredNumPages, curShmAll)); } } } void* align(size_t alignment, size_t size, void*& ptr, size_t& space) { XDCHECK(folly::isPowTwo(alignment)); const size_t alignmentMask = ~(alignment - 1); auto alignedPtr = reinterpret_cast<uint8_t*>( (reinterpret_cast<uintptr_t>(ptr) & alignmentMask)); // not properly aligned so set it to the next aligned address. if (alignedPtr != ptr) { alignedPtr += alignment; } const ptrdiff_t diff = alignedPtr - reinterpret_cast<uint8_t*>(ptr); if ((diff + size) <= space) { ptr = reinterpret_cast<void*>(alignedPtr); space -= diff; return ptr; } return nullptr; } void* mmapAlignedZeroedMemory(size_t alignment, size_t numBytes, bool noAccess) { // to enforce alignment, we try to make sure that the address we return is // aligned to slab size. size_t newBytes = numBytes + alignment; const auto protFlag = noAccess ? PROT_NONE : PROT_READ | PROT_WRITE; const auto mapFlag = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE; void* memory = mmap(nullptr, newBytes, protFlag, mapFlag, -1, 0); if (memory != MAP_FAILED) { auto alignedMemory = align(alignment, numBytes, memory, newBytes); XDCHECK_NE(alignedMemory, nullptr); return alignedMemory; } throw std::system_error(errno, std::system_category(), "Cannot mmap"); } void setMaxLockMemory(uint64_t bytes) { struct rlimit rlim { bytes, bytes }; const int rv = setrlimit(RLIMIT_MEMLOCK, &rlim); if (rv == 0) { return; } throw std::system_error( errno, std::system_category(), folly::sformat("Error setting rlimit to {} bytes. Errno = {}", bytes, errno)); } size_t getNumResidentPages(const void* memory, size_t len) { if (!isPageAlignedAddr(memory)) { throw std::invalid_argument( folly::sformat("addr {} is not page aligned", memory)); } XDCHECK(isPageAlignedAddr(memory)); const size_t numPages = getNumPages(len); // TODO this could be a large allocation. may be break it up if it matters. std::vector<unsigned char> vec(numPages, 0); const int rv = mincore(const_cast<void*>(memory), len, vec.data()); if (rv != 0) { throw std::system_error( errno, std::system_category(), folly::sformat("Error in mincore addr = {}, len = {}. errno = {}", memory, len, errno)); } return std::count_if(vec.begin(), vec.end(), [](unsigned char c) { return c != 0; }); } size_t getPageSize() noexcept { static long pagesize = sysconf(_SC_PAGESIZE); XDCHECK_NE(pagesize, -1); XDCHECK_GT(pagesize, 0); return pagesize; } size_t getNumPages(size_t len) noexcept { return (len + getPageSize() - 1) / getPageSize(); } bool isPageAlignedAddr(const void* addr) noexcept { return reinterpret_cast<uintptr_t>(addr) % getPageSize() == 0; } /* returns true with the file's mode. false if the file does not exist. * throws system_error for all other errors */ bool getStatIfExists(const std::string& name, mode_t* mode) { struct stat buf = {}; const int ret = stat(name.c_str(), &buf); if (ret == 0) { if (mode != nullptr) { *mode = buf.st_mode; } return true; } else if (errno != ENOENT && errno != ENOTDIR) { // some system error, but it might exist throwSystemError(errno, folly::sformat("Path: {}", name)); } // does not exist; return false; } bool pathExists(const std::string& path) { return getStatIfExists(path, nullptr); } bool isDir(const std::string& name) { struct stat buf = {}; auto err = stat(name.c_str(), &buf); if (err) { throwSystemError(errno, folly::sformat("Path: {}", name)); } return S_ISDIR(buf.st_mode) ? true : false; } /* throws error on any failure. */ void makeDir(const std::string& name) { auto mkdirs = [](const std::string& path, mode_t mode) { char tmp[256]; char* p = nullptr; size_t len; snprintf(tmp, sizeof(tmp), "%s", path.c_str()); len = strlen(tmp); if (len == 0) { throw std::invalid_argument( folly::sformat("Error forming path {}", path)); } if (tmp[len - 1] == '/') { tmp[len - 1] = 0; } for (p = tmp + 1; *p; p++) { if (*p == '/') { *p = 0; SCOPE_EXIT { *p = '/'; }; if (mkdir(tmp, mode) != 0 && errno != EEXIST) { throwSystemError(errno, folly::sformat("failed to create {}", tmp)); } *p = '/'; } } // error checked by caller mkdir(tmp, mode); }; mkdirs(name.c_str(), 0777); if (!getStatIfExists(name, nullptr)) { throwSystemError(errno, folly::sformat("{} expected to be existing", name)); } } /* throws error on any failure. */ void removePath(const std::string& name) { if (!pathExists(name)) { return; } if (isDir(name)) { auto dir = opendir(name.c_str()); if (!dir) { throwSystemError(errno, folly::sformat("Err removing path={}", name)); } SCOPE_EXIT { free(dir); }; struct dirent* entry; while ((entry = readdir(dir))) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } std::string path = name + "/" + std::string(entry->d_name); removePath(path); } auto err = rmdir(name.c_str()); if (err) { throwSystemError(errno, folly::sformat("Err removing path={}", name)); } } else { auto err = unlink(name.c_str()); if (err) { throwSystemError(errno, folly::sformat("Err removing path={}", name)); } } } std::string getUniqueTempDir(folly::StringPiece prefix) { const char* dir = getenv("TMPDIR"); if (dir == nullptr) { dir = "/tmp"; } return folly::sformat("{}/{}_{}", dir, prefix, folly::Random::rand32()); } std::string toString(std::chrono::nanoseconds d) { // bunch of constants to help us round up and convert with appropriate // suffix. For example say 90ns or 5.6us or 5.5ms or 55s etc constexpr uint64_t micros = std::chrono::nanoseconds(std::chrono::microseconds(1)).count(); constexpr uint64_t millis = std::chrono::nanoseconds(std::chrono::milliseconds(1)).count(); constexpr uint64_t secs = std::chrono::nanoseconds(std::chrono::seconds(1)).count(); uint64_t count = d.count(); if (count < micros) { return folly::sformat("{}ns", count); } else if (count < millis) { return folly::sformat("{:.2f}us", static_cast<double>(count) / micros); } else if (count < secs) { return folly::sformat("{:.2f}ms", static_cast<double>(count) / millis); } else { return folly::sformat("{:.2f}s", static_cast<double>(count) / secs); } } namespace { // char to int conversion bool isDigit(char c) { return std::isdigit(c); } } // namespace size_t getRSSBytes() { // read field 2 from /proc/self/statm according to // http://man7.org/linux/man-pages/man5/proc.5.html std::string memInfoStr; if (!folly::readFile("/proc/self/statm", memInfoStr)) { return 0; } XDCHECK(!std::isdigit(memInfoStr.back())); memInfoStr.pop_back(); std::vector<folly::StringPiece> tokens; folly::split(' ', memInfoStr, tokens); // 7 numeric fields followed by carriage return XDCHECK_GE(tokens.size(), 7ULL); if (tokens.size() < 7) { return 0; } for (auto t : tokens) { XDCHECK_EQ(std::find_if_not(t.begin(), t.end(), isDigit), t.end()); } size_t pages = folly::to<size_t>(tokens[1]) + 3; return pages * getPageSize(); } size_t getMemAvailable() { // read MemAvailable line from /proc/meminfo std::string memInfoStr; if (!folly::readFile("/proc/meminfo", memInfoStr)) { return 0; } std::vector<folly::StringPiece> lines; folly::split('\n', memInfoStr, lines); constexpr folly::StringPiece memAvailStr{"MemAvailable"}; for (auto l : lines) { if (l.startsWith(memAvailStr)) { // format is MemAvailable: 172048584 kB auto startIt = std::find_if(l.begin(), l.end(), isDigit); XDCHECK_NE(startIt, l.end()); // last 3 chars are ' kB' auto endIt = l.end() - 3; XDCHECK_EQ(std::find_if_not(startIt, l.end(), isDigit), endIt); return folly::to<size_t>(folly::StringPiece{startIt, endIt}) * 1024ULL; } } return 0; } void printExceptionStackTraces() { auto exceptions = folly::exception_tracer::getCurrentExceptions(); for (auto& exc : exceptions) { std::cerr << exc << std::endl; } } } // namespace util } // namespace cachelib } // namespace facebook