Copyright 2012 -> 2013
[folly.git] / folly / experimental / io / HugePages.cpp
1 /*
2  * Copyright 2013 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "folly/experimental/io/HugePages.h"
18
19 #include <sys/mman.h>
20 #include <sys/stat.h>
21 #include <sys/types.h>
22 #include <fcntl.h>
23
24 #include <cctype>
25 #include <cstring>
26
27 #include <algorithm>
28 #include <stdexcept>
29 #include <system_error>
30
31 #include <boost/noncopyable.hpp>
32 #include <boost/regex.hpp>
33
34 #include <glog/logging.h>
35
36 #include "folly/Conv.h"
37 #include "folly/Format.h"
38 #include "folly/Range.h"
39 #include "folly/ScopeGuard.h"
40 #include "folly/String.h"
41
42 #include "folly/experimental/Gen.h"
43 #include "folly/experimental/FileGen.h"
44 #include "folly/experimental/StringGen.h"
45
46 namespace folly {
47
48 namespace {
49
50 // Get the default huge page size
51 size_t getDefaultHugePageSize() {
52   // We need to parse /proc/meminfo
53   static const boost::regex regex(R"!(Hugepagesize:\s*(\d+)\s*kB)!");
54   size_t pageSize = 0;
55   boost::cmatch match;
56
57   bool error = gen::byLine("/proc/meminfo") |
58     [&] (StringPiece line) -> bool {
59       if (boost::regex_match(line.begin(), line.end(), match, regex)) {
60         StringPiece numStr(line.begin() + match.position(1), match.length(1));
61         pageSize = to<size_t>(numStr) * 1024;  // in KiB
62         return false;  // stop
63       }
64       return true;
65     };
66
67   if (error) {
68     throw std::runtime_error("Can't find default huge page size");
69   }
70   return pageSize;
71 }
72
73 // Get raw huge page sizes (without mount points, they'll be filled later)
74 HugePageSizeVec getRawHugePageSizes() {
75   // We need to parse file names from /sys/kernel/mm/hugepages
76   static const boost::regex regex(R"!(hugepages-(\d+)kB)!");
77   boost::smatch match;
78   HugePageSizeVec vec;
79   fs::path path("/sys/kernel/mm/hugepages");
80   for (fs::directory_iterator it(path); it != fs::directory_iterator(); ++it) {
81     std::string filename(it->path().filename().native());
82     if (boost::regex_match(filename, match, regex)) {
83       StringPiece numStr(filename.data() + match.position(1), match.length(1));
84       vec.emplace_back(to<size_t>(numStr) * 1024);
85     }
86   }
87   return vec;
88 }
89
90 // Parse the value of a pagesize mount option
91 // Format: number, optional K/M/G/T suffix, trailing junk allowed
92 size_t parsePageSizeValue(StringPiece value) {
93   static const boost::regex regex(R"!((\d+)([kmgt])?.*)!", boost::regex::icase);
94   boost::cmatch match;
95   if (!boost::regex_match(value.begin(), value.end(), match, regex)) {
96     throw std::runtime_error("Invalid pagesize option");
97   }
98   char c = '\0';
99   if (match.length(2) != 0) {
100     c = tolower(value[match.position(2)]);
101   }
102   StringPiece numStr(value.data() + match.position(1), match.length(1));
103   size_t size = to<size_t>(numStr);
104   switch (c) {
105   case 't': size *= 1024;
106   case 'g': size *= 1024;
107   case 'm': size *= 1024;
108   case 'k': size *= 1024;
109   }
110   return size;
111 }
112
113 /**
114  * Get list of supported huge page sizes and their mount points, if
115  * hugetlbfs file systems are mounted for those sizes.
116  */
117 HugePageSizeVec getHugePageSizes() {
118   HugePageSizeVec sizeVec = getRawHugePageSizes();
119   if (sizeVec.empty()) {
120     return sizeVec;  // nothing to do
121   }
122   std::sort(sizeVec.begin(), sizeVec.end());
123
124   size_t defaultHugePageSize = getDefaultHugePageSize();
125
126   struct PageSizeLess {
127     bool operator()(const HugePageSize& a, size_t b) const {
128       return a.size < b;
129     }
130     bool operator()(size_t a, const HugePageSize& b) const {
131       return a < b.size;
132     }
133   };
134
135   // Read and parse /proc/mounts
136   std::vector<StringPiece> parts;
137   std::vector<StringPiece> options;
138
139   gen::byLine("/proc/mounts") | gen::eachAs<StringPiece>() |
140     [&](StringPiece line) {
141       parts.clear();
142       split(" ", line, parts);
143       // device path fstype options uid gid
144       if (parts.size() != 6) {
145         throw std::runtime_error("Invalid /proc/mounts line");
146       }
147       if (parts[2] != "hugetlbfs") {
148         return;  // we only care about hugetlbfs
149       }
150
151       options.clear();
152       split(",", parts[3], options);
153       size_t pageSize = defaultHugePageSize;
154       // Search for the "pagesize" option, which must have a value
155       for (auto& option : options) {
156         // key=value
157         const char* p = static_cast<const char*>(
158             memchr(option.data(), '=', option.size()));
159         if (!p) {
160           continue;
161         }
162         if (StringPiece(option.data(), p) != "pagesize") {
163           continue;
164         }
165         pageSize = parsePageSizeValue(StringPiece(p + 1, option.end()));
166         break;
167       }
168
169       auto pos = std::lower_bound(sizeVec.begin(), sizeVec.end(), pageSize,
170                                   PageSizeLess());
171       if (pos == sizeVec.end() || pos->size != pageSize) {
172         throw std::runtime_error("Mount page size not found");
173       }
174       if (pos->mountPoint.empty()) {
175         // Store mount point
176         pos->mountPoint = fs::canonical(fs::path(parts[1].begin(),
177                                                  parts[1].end()));
178       }
179     };
180
181   return sizeVec;
182 }
183
184 // RAII wrapper around an open file, closes on exit unless you call release()
185 class ScopedFd : private boost::noncopyable {
186  public:
187   explicit ScopedFd(int fd) : fd_(fd) { }
188   int fd() const { return fd_; }
189
190   void release() {
191     fd_ = -1;
192   }
193
194   void close() {
195     if (fd_ == -1) {
196       return;
197     }
198     int r = ::close(fd_);
199     fd_ = -1;
200     if (r == -1) {
201       throw std::system_error(errno, std::system_category(), "close failed");
202     }
203   }
204
205   ~ScopedFd() {
206     try {
207       close();
208     } catch (...) {
209       PLOG(ERROR) << "close failed!";
210     }
211   }
212
213  private:
214   int fd_;
215 };
216
217 // RAII wrapper that deletes a file upon destruction unless you call release()
218 class ScopedDeleter : private boost::noncopyable {
219  public:
220   explicit ScopedDeleter(fs::path name) : name_(std::move(name)) { }
221   void release() {
222     name_.clear();
223   }
224
225   ~ScopedDeleter() {
226     if (name_.empty()) {
227       return;
228     }
229     int r = ::unlink(name_.c_str());
230     if (r == -1) {
231       PLOG(ERROR) << "unlink failed";
232     }
233   }
234  private:
235   fs::path name_;
236 };
237
238 // RAII wrapper around a mmap mapping, munmaps upon destruction unless you
239 // call release()
240 class ScopedMmap : private boost::noncopyable {
241  public:
242   ScopedMmap(void* start, size_t size) : start_(start), size_(size) { }
243
244   void* start() const { return start_; }
245   size_t size() const { return size_; }
246
247   void release() {
248     start_ = MAP_FAILED;
249   }
250
251   void munmap() {
252     if (start_ == MAP_FAILED) {
253       return;
254     }
255     int r = ::munmap(start_, size_);
256     start_ = MAP_FAILED;
257     if (r == -1) {
258       throw std::system_error(errno, std::system_category(), "munmap failed");
259     }
260   }
261
262   ~ScopedMmap() {
263     try {
264       munmap();
265     } catch (...) {
266       PLOG(ERROR) << "munmap failed!";
267     }
268   }
269  private:
270   void* start_;
271   size_t size_;
272 };
273
274 }  // namespace
275
276 HugePages::HugePages() : sizes_(getHugePageSizes()) { }
277
278 const HugePageSize& HugePages::getSize(size_t hugePageSize) const {
279   // Linear search is just fine.
280   for (auto& p : sizes_) {
281     if (p.mountPoint.empty()) {
282       continue;  // not mounted
283     }
284     if (hugePageSize == 0 || hugePageSize == p.size) {
285       return p;
286     }
287   }
288   throw std::runtime_error("Huge page not supported / not mounted");
289 }
290
291 HugePages::File HugePages::create(ByteRange data,
292                                   const fs::path& path,
293                                   HugePageSize hugePageSize) const {
294   namespace bsys = ::boost::system;
295   if (hugePageSize.size == 0) {
296     hugePageSize = getSize();
297   }
298
299   // Round size up
300   File file;
301   file.size = data.size() / hugePageSize.size * hugePageSize.size;
302   if (file.size != data.size()) {
303     file.size += hugePageSize.size;
304   }
305
306   {
307     file.path = fs::canonical_parent(path, hugePageSize.mountPoint);
308     if (!fs::starts_with(file.path, hugePageSize.mountPoint)) {
309       throw fs::filesystem_error(
310           "HugePages::create: path not rooted at mount point",
311           file.path, hugePageSize.mountPoint,
312           bsys::errc::make_error_code(bsys::errc::invalid_argument));
313     }
314   }
315   ScopedFd fd(open(file.path.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666));
316   if (fd.fd() == -1) {
317     throw std::system_error(errno, std::system_category(), "open failed");
318   }
319
320   ScopedDeleter deleter(file.path);
321
322   ScopedMmap map(mmap(nullptr, file.size, PROT_READ | PROT_WRITE,
323                       MAP_SHARED | MAP_POPULATE, fd.fd(), 0),
324                  file.size);
325   if (map.start() == MAP_FAILED) {
326     throw std::system_error(errno, std::system_category(), "mmap failed");
327   }
328
329   // Ignore madvise return code
330   madvise(const_cast<unsigned char*>(data.data()), data.size(),
331           MADV_SEQUENTIAL);
332   // Why is this not memcpy, you ask?
333   // The SSSE3-optimized memcpy in glibc likes to copy memory backwards,
334   // rendering any prefetching from madvise useless (even harmful).
335   const unsigned char* src = data.data();
336   size_t size = data.size();
337   unsigned char* dest = reinterpret_cast<unsigned char*>(map.start());
338   if (reinterpret_cast<uintptr_t>(src) % 8 == 0) {
339     const uint64_t* src8 = reinterpret_cast<const uint64_t*>(src);
340     size_t size8 = size / 8;
341     uint64_t* dest8 = reinterpret_cast<uint64_t*>(dest);
342     while (size8--) {
343       *dest8++ = *src8++;
344     }
345     src = reinterpret_cast<const unsigned char*>(src8);
346     dest = reinterpret_cast<unsigned char*>(dest8);
347     size %= 8;
348   }
349   memcpy(dest, src, size);
350
351   map.munmap();
352   deleter.release();
353   fd.close();
354
355   return file;
356 }
357
358 }  // namespace folly
359