diff --git a/README.md b/README.md index 6f5f46d4..4e1a62e0 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ mergerfs does **not** support the copy-on-write (CoW) behavior found in **aufs** * **func.<func>=<policy>**: sets the specific FUSE function's policy. See below for the list of value types. Example: **func.getattr=newest** * **category.<category>=<policy>**: Sets policy of all FUSE functions in the provided category. Example: **category.create=mfs** * **cache.open=**: 'open' policy cache timeout in seconds. (default: 0) +* **cache.statfs=**: 'statfs' cache timeout in seconds. (default: 0) **NOTE:** Options are evaluated in the order listed so if the options are **func.rmdir=rand,category.action=ff** the **action** category setting will override the **rmdir** setting. @@ -501,6 +502,13 @@ The `open` policy cache will cache the result of an `open` policy for a particul This cache is useful in cases like that of **Transmission** which has a "open, read/write, close" pattern (which is much more costly due to the FUSE overhead than normal.) +#### statfs caching + +Of the syscalls used by mergerfs in policies the `statfs` / `statvfs` call is perhaps the most expensive. It's used to find out the available space of a drive and whether it is mounted read-only. Depending on the setup and usage pattern these queries can be relatively costly. When `cache.statfs` is enabled all calls to `statfs` by a policy will be cached for the number of seconds its set to. + +Example: If the create policy is `mfs` and the timeout is 60 then for that 60 seconds the same drive will be returned as the target for creates because the available space won't be updated for that time. + + #### writeback caching writeback caching is a technique for improving write speeds by batching writes at a faster device and then bulk writing to the slower device. With FUSE the kernel will wait for a number of writes to be made and then send it to the filesystem as one request. mergerfs currently uses a slightly modified and vendored libfuse 2.9.7 which does not support writeback caching. However, a prototype port to libfuse 3.x has been made and the writeback cache appears to work as expected (though performance improvements greatly depend on the way the client app writes data). Once the port is complete and thoroughly tested writeback caching will be available. diff --git a/man/mergerfs.1 b/man/mergerfs.1 index 20796fc9..3862cf94 100644 --- a/man/mergerfs.1 +++ b/man/mergerfs.1 @@ -214,6 +214,9 @@ Example: \f[B]category.create=mfs\f[] .IP \[bu] 2 \f[B]cache.open=\f[]: \[aq]open\[aq] policy cache timeout in seconds. (default: 0) +.IP \[bu] 2 +\f[B]cache.statfs=\f[]: \[aq]statfs\[aq] cache timeout in seconds. +(default: 0) .PP \f[B]NOTE:\f[] Options are evaluated in the order listed so if the options are \f[B]func.rmdir=rand,category.action=ff\f[] the @@ -1054,6 +1057,20 @@ expired entries. This cache is useful in cases like that of \f[B]Transmission\f[] which has a "open, read/write, close" pattern (which is much more costly due to the FUSE overhead than normal.) +.SS statfs caching +.PP +Of the syscalls used by mergerfs in policies the \f[C]statfs\f[] / +\f[C]statvfs\f[] call is perhaps the most expensive. +It\[aq]s used to find out the available space of a drive and whether it +is mounted read\-only. +Depending on the setup and usage pattern these queries can be relatively +costly. +When \f[C]cache.statfs\f[] is enabled all calls to \f[C]statfs\f[] by a +policy will be cached for the number of seconds its set to. +.PP +Example: If the create policy is \f[C]mfs\f[] and the timeout is 60 then +for that 60 seconds the same drive will be returned as the target for +creates because the available space won\[aq]t be updated for that time. .SS writeback caching .PP writeback caching is a technique for improving write speeds by batching diff --git a/src/fs.cpp b/src/fs.cpp index ae323cd3..a587f0cb 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -26,11 +26,10 @@ #include "fs_attr.hpp" #include "fs_base_realpath.hpp" #include "fs_base_stat.hpp" -#include "fs_base_statvfs.hpp" #include "fs_exists.hpp" #include "fs_path.hpp" +#include "fs_statvfs_cache.hpp" #include "fs_xattr.hpp" -#include "statvfs_util.hpp" #include "str.hpp" using std::string; @@ -38,48 +37,6 @@ using std::vector; namespace fs { - int - readonly(const string *path_, - bool *readonly_) - { - int rv; - struct statvfs st; - - rv = fs::statvfs(*path_,st); - if(rv == 0) - *readonly_ = StatVFS::readonly(st); - - return rv; - } - - int - spaceavail(const string *path_, - uint64_t *spaceavail_) - { - int rv; - struct statvfs st; - - rv = fs::statvfs(*path_,st); - if(rv == 0) - *spaceavail_ = StatVFS::spaceavail(st); - - return rv; - } - - int - spaceused(const string *path_, - uint64_t *spaceused_) - { - int rv; - struct statvfs st; - - rv = fs::statvfs(*path_,st); - if(rv == 0) - *spaceused_ = StatVFS::spaceused(st); - - return rv; - } - void findallfiles(const vector &basepaths, const char *fusepath, @@ -171,16 +128,13 @@ namespace fs int rv; uint64_t mfs; uint64_t spaceavail; - const string *basepath; const string *mfsbasepath; mfs = 0; mfsbasepath = NULL; for(size_t i = 0, ei = basepaths.size(); i != ei; i++) { - basepath = &basepaths[i]; - - rv = fs::spaceavail(basepath,&spaceavail); + rv = fs::statvfs_cache_spaceavail(basepaths[i],&spaceavail); if(rv == -1) continue; if(spaceavail < minfreespace) diff --git a/src/fs.hpp b/src/fs.hpp index 7f3dc2ab..7d439b85 100644 --- a/src/fs.hpp +++ b/src/fs.hpp @@ -27,15 +27,6 @@ namespace fs using std::string; using std::vector; - int readonly(const string *path_, - bool *readonly_); - - int spaceavail(const string *path_, - uint64_t *spaceavail_); - - int spaceused(const string *path_, - uint64_t *spaceavail_); - void findallfiles(const vector &basepaths, const char *fusepath, vector &paths); diff --git a/src/fs_base_statvfs.hpp b/src/fs_base_statvfs.hpp index b8ee4325..2ba2567f 100644 --- a/src/fs_base_statvfs.hpp +++ b/src/fs_base_statvfs.hpp @@ -1,7 +1,7 @@ /* ISC License - Copyright (c) 2016, Antonio SJ Musumeci + Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -35,35 +35,35 @@ namespace fs static inline int - statvfs(const char *path, - struct statvfs &st) + statvfs(const char *path_, + struct statvfs *st_) { - return ::statvfs(path,&st); + return ::statvfs(path_,st_); } static inline int - statvfs(const std::string &path, - struct statvfs &st) + statvfs(const std::string &path_, + struct statvfs *st_) { - return fs::statvfs(path.c_str(),st); + return fs::statvfs(path_.c_str(),st_); } static inline int fstatvfs(const int fd_, - struct statvfs &st_) + struct statvfs *st_) { - return ::fstatvfs(fd_,&st_); + return ::fstatvfs(fd_,st_); } static inline int lstatvfs(const std::string &path_, - struct statvfs &st_) + struct statvfs *st_) { int fd; int rv; diff --git a/src/fs_info.cpp b/src/fs_info.cpp index 4850e45e..1c29c205 100644 --- a/src/fs_info.cpp +++ b/src/fs_info.cpp @@ -20,6 +20,7 @@ #include "fs_base_statvfs.hpp" #include "fs_info_t.hpp" #include "fs_path.hpp" +#include "fs_statvfs_cache.hpp" #include "statvfs_util.hpp" #include @@ -37,7 +38,7 @@ namespace fs int rv; struct statvfs st; - rv = fs::statvfs(*path_,st); + rv = fs::statvfs_cache(path_->c_str(),&st); if(rv == 0) { info_->readonly = StatVFS::readonly(st); diff --git a/src/fs_statvfs_cache.cpp b/src/fs_statvfs_cache.cpp new file mode 100644 index 00000000..2f9aa683 --- /dev/null +++ b/src/fs_statvfs_cache.cpp @@ -0,0 +1,145 @@ +/* + ISC License + + Copyright (c) 2016, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "fs_base_statvfs.hpp" +#include "statvfs_util.hpp" + +#include +#include + +#include +#include +#include +#include + +struct Element +{ + uint64_t time; + struct statvfs st; +}; + +typedef std::map statvfs_cache; + +static uint64_t g_timeout = 0; +static statvfs_cache g_cache; +static pthread_mutex_t g_cache_lock = PTHREAD_MUTEX_INITIALIZER; + +namespace local +{ + static + uint64_t + get_time(void) + { + uint64_t rv; + struct timeval now; + + ::gettimeofday(&now,NULL); + + rv = now.tv_sec; + + return rv; + } +} + +namespace fs +{ + uint64_t + statvfs_cache_timeout(void) + { + return g_timeout; + } + + void + statvfs_cache_timeout(const uint64_t timeout_) + { + g_timeout = timeout_; + } + + int + statvfs_cache(const char *path_, + struct statvfs *st_) + { + int rv; + Element *e; + uint64_t now; + + if(g_timeout == 0) + return fs::statvfs(path_,st_); + + rv = 0; + now = local::get_time(); + + pthread_mutex_lock(&g_cache_lock); + + e = &g_cache[path_]; + + if((now - e->time) > g_timeout) + { + e->time = now; + rv = fs::statvfs(path_,&e->st); + } + + *st_ = e->st; + + pthread_mutex_unlock(&g_cache_lock); + + return rv; + } + + int + statvfs_cache_readonly(const std::string &path_, + bool *readonly_) + { + int rv; + struct statvfs st; + + rv = fs::statvfs_cache(path_.c_str(),&st); + if(rv == 0) + *readonly_ = StatVFS::readonly(st); + + return rv; + } + + int + statvfs_cache_spaceavail(const std::string &path_, + uint64_t *spaceavail_) + { + int rv; + struct statvfs st; + + rv = fs::statvfs_cache(path_.c_str(),&st); + if(rv == 0) + *spaceavail_ = StatVFS::spaceavail(st); + + return rv; + } + + int + statvfs_cache_spaceused(const std::string &path_, + uint64_t *spaceused_) + { + int rv; + struct statvfs st; + + rv = fs::statvfs_cache(path_.c_str(),&st); + if(rv == 0) + *spaceused_ = StatVFS::spaceused(st); + + return rv; + } +} diff --git a/src/fs_statvfs_cache.hpp b/src/fs_statvfs_cache.hpp new file mode 100644 index 00000000..7ebce2b2 --- /dev/null +++ b/src/fs_statvfs_cache.hpp @@ -0,0 +1,44 @@ +/* + ISC License + + Copyright (c) 2019, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include +#include + +namespace fs +{ + uint64_t + statvfs_cache_timeout(void); + void + statvfs_cache_timeout(const uint64_t timeout_); + + int + statvfs_cache(const char *path_, + struct statvfs *st_); + + int + statvfs_cache_readonly(const std::string &path_, + bool *readonly_); + + int + statvfs_cache_spaceavail(const std::string &path_, + uint64_t *spaceavail_); + + int + statvfs_cache_spaceused(const std::string &path_, + uint64_t *spaceused_); +} diff --git a/src/getxattr.cpp b/src/getxattr.cpp index 6c9d27e6..b0365668 100644 --- a/src/getxattr.cpp +++ b/src/getxattr.cpp @@ -18,6 +18,7 @@ #include "errno.hpp" #include "fs_base_getxattr.hpp" #include "fs_path.hpp" +#include "fs_statvfs_cache.hpp" #include "rwlock.hpp" #include "str.hpp" #include "ugid.hpp" @@ -308,6 +309,8 @@ _getxattr_controlfile(const Config &config, _getxattr_controlfile_fusefunc_policy(config,attr[3],attrvalue); else if((attr[2] == "cache") && (attr[3] == "open")) _getxattr_controlfile_uint64_t(config.open_cache.timeout,attrvalue); + else if((attr[2] == "cache") && (attr[3] == "statfs")) + _getxattr_controlfile_uint64_t(fs::statvfs_cache_timeout(),attrvalue); break; } diff --git a/src/listxattr.cpp b/src/listxattr.cpp index dfa794da..d89949b0 100644 --- a/src/listxattr.cpp +++ b/src/listxattr.cpp @@ -45,6 +45,7 @@ _listxattr_controlfile(char *list, buildvector ("user.mergerfs.branches") ("user.mergerfs.cache.open") + ("user.mergerfs.cache.statfs") ("user.mergerfs.direct_io") ("user.mergerfs.dropcacheonclose") ("user.mergerfs.ignorepponrename") diff --git a/src/option_parser.cpp b/src/option_parser.cpp index 874089d9..f71175f3 100644 --- a/src/option_parser.cpp +++ b/src/option_parser.cpp @@ -17,6 +17,7 @@ #include "config.hpp" #include "errno.hpp" #include "fs_glob.hpp" +#include "fs_statvfs_cache.hpp" #include "num.hpp" #include "policy.hpp" #include "str.hpp" @@ -112,12 +113,12 @@ set_default_options(fuse_args &args) static int -parse_and_process(const std::string &value, - uint64_t &minfreespace) +parse_and_process(const std::string &value_, + uint64_t &int_) { int rv; - rv = num::to_uint64_t(value,minfreespace); + rv = num::to_uint64_t(value_,int_); if(rv == -1) return 1; @@ -204,12 +205,30 @@ parse_and_process_statfsignore(const std::string &value_, static int -parse_and_process_cache(Config &config_, +parse_and_process_statfs_cache(const std::string &value_) +{ + int rv; + uint64_t timeout; + + rv = num::to_uint64_t(value_,timeout); + if(rv == -1) + return 1; + + fs::statvfs_cache_timeout(timeout); + + return 0; +} + +static +int +parse_and_process_cache(Config &config_, const string &func_, const string &value_) { if(func_ == "open") return parse_and_process(value_,config_.open_cache.timeout); + else if(func_ == "statfs") + return parse_and_process_statfs_cache(value_); return 1; } @@ -352,6 +371,8 @@ usage(void) " -o category.=

Set functions in category to

\n" " -o cache.open= 'open' policy cache timeout in seconds.\n" " default = 0 (disabled)\n" + " -o cache.statfs= 'statfs' cache timeout in seconds. Used by\n" + " policies. default = 0 (disabled)\n" " -o direct_io Bypass page caching, may increase write\n" " speeds at the cost of reads. Please read docs\n" " for more details as there are tradeoffs.\n" diff --git a/src/policy_epall.cpp b/src/policy_epall.cpp index 363c26cb..a237caf4 100644 --- a/src/policy_epall.cpp +++ b/src/policy_epall.cpp @@ -19,6 +19,7 @@ #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" +#include "fs_statvfs_cache.hpp" #include "policy.hpp" #include "policy_error.hpp" @@ -88,7 +89,7 @@ namespace epall error_and_continue(error,ENOENT); if(branch->ro()) error_and_continue(error,EROFS); - rv = fs::readonly(&branch->path,&readonly); + rv = fs::statvfs_cache_readonly(branch->path,&readonly); if(rv == -1) error_and_continue(error,ENOENT); if(readonly) diff --git a/src/policy_epff.cpp b/src/policy_epff.cpp index e9a50135..39f9cf56 100644 --- a/src/policy_epff.cpp +++ b/src/policy_epff.cpp @@ -19,6 +19,7 @@ #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" +#include "fs_statvfs_cache.hpp" #include "policy.hpp" #include "policy_error.hpp" @@ -87,7 +88,7 @@ namespace epff error_and_continue(error,ENOENT); if(branch->ro()) error_and_continue(error,EROFS); - rv = fs::readonly(&branch->path,&readonly); + rv = fs::statvfs_cache_readonly(branch->path,&readonly); if(rv == -1) error_and_continue(error,ENOENT); if(readonly) diff --git a/src/policy_eplfs.cpp b/src/policy_eplfs.cpp index a481b0c0..388c3fff 100644 --- a/src/policy_eplfs.cpp +++ b/src/policy_eplfs.cpp @@ -19,6 +19,7 @@ #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" +#include "fs_statvfs_cache.hpp" #include "policy.hpp" #include "policy_error.hpp" @@ -142,7 +143,7 @@ namespace eplfs if(!fs::exists(branch->path,fusepath)) continue; - rv = fs::spaceavail(&branch->path,&spaceavail); + rv = fs::statvfs_cache_spaceavail(branch->path,&spaceavail); if(rv == -1) continue; if(spaceavail > eplfs) diff --git a/src/policy_eplus.cpp b/src/policy_eplus.cpp index 82accc0c..c11ab070 100644 --- a/src/policy_eplus.cpp +++ b/src/policy_eplus.cpp @@ -19,6 +19,7 @@ #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" +#include "fs_statvfs_cache.hpp" #include "policy.hpp" #include "policy_error.hpp" @@ -142,7 +143,7 @@ namespace eplus if(!fs::exists(branch->path,fusepath)) continue; - rv = fs::spaceused(&branch->path,&spaceused); + rv = fs::statvfs_cache_spaceused(branch->path,&spaceused); if(rv == -1) continue; if(spaceused >= eplus) diff --git a/src/policy_epmfs.cpp b/src/policy_epmfs.cpp index 3af1a02a..904447f7 100644 --- a/src/policy_epmfs.cpp +++ b/src/policy_epmfs.cpp @@ -19,6 +19,7 @@ #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" +#include "fs_statvfs_cache.hpp" #include "policy.hpp" #include "policy_error.hpp" @@ -142,7 +143,7 @@ namespace epmfs if(!fs::exists(branch->path,fusepath)) continue; - rv = fs::spaceavail(&branch->path,&spaceavail); + rv = fs::statvfs_cache_spaceavail(branch->path,&spaceavail); if(rv == -1) continue; if(spaceavail < epmfs) diff --git a/src/policy_newest.cpp b/src/policy_newest.cpp index e7331f01..f4f8a249 100644 --- a/src/policy_newest.cpp +++ b/src/policy_newest.cpp @@ -19,6 +19,7 @@ #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" +#include "fs_statvfs_cache.hpp" #include "policy.hpp" #include "policy_error.hpp" @@ -108,7 +109,7 @@ namespace newest error_and_continue(error,EROFS); if(st.st_mtime < newest) continue; - rv = fs::readonly(&branch->path,&readonly); + rv = fs::statvfs_cache_readonly(branch->path,&readonly); if(rv == -1) error_and_continue(error,ENOENT); if(readonly) diff --git a/src/release.cpp b/src/release.cpp index 56241ab8..f68030b0 100644 --- a/src/release.cpp +++ b/src/release.cpp @@ -58,7 +58,8 @@ namespace mergerfs const Config &config = Config::get(); FileInfo *fi = reinterpret_cast(ffi_->fh); - config.open_cache.cleanup(10); + if(config.open_cache.timeout) + config.open_cache.cleanup(10); return local::release(fi,config.dropcacheonclose); } diff --git a/src/releasedir.cpp b/src/releasedir.cpp index 4f3ee1e8..8c437de8 100644 --- a/src/releasedir.cpp +++ b/src/releasedir.cpp @@ -39,10 +39,7 @@ namespace mergerfs releasedir(const char *fusepath_, fuse_file_info *ffi_) { - const Config &config = Config::get(); - DirInfo *di = reinterpret_cast(ffi_->fh); - - config.open_cache.cleanup(10); + DirInfo *di = reinterpret_cast(ffi_->fh); return local::releasedir(di); } diff --git a/src/setxattr.cpp b/src/setxattr.cpp index e39523ff..c27963e7 100644 --- a/src/setxattr.cpp +++ b/src/setxattr.cpp @@ -19,6 +19,7 @@ #include "fs_base_setxattr.hpp" #include "fs_glob.hpp" #include "fs_path.hpp" +#include "fs_statvfs_cache.hpp" #include "num.hpp" #include "rv.hpp" #include "rwlock.hpp" @@ -260,6 +261,21 @@ _setxattr_controlfile_category_policy(Config &config, return 0; } +static +int +_setxattr_statfs_timeout(const string &attrval_, + const int flags_) +{ + int rv; + uint64_t timeout; + + rv = _setxattr_uint64_t(attrval_,flags_,timeout); + if(rv >= 0) + fs::statvfs_cache_timeout(timeout); + + return rv; +} + static int _setxattr_controlfile(Config &config, @@ -349,6 +365,8 @@ _setxattr_controlfile(Config &config, return _setxattr_uint64_t(attrval, flags, config.open_cache.timeout); + else if((attr[2] == "cache") && (attr[3] == "statfs")) + return _setxattr_statfs_timeout(attrval,flags); break; default: diff --git a/src/statfs.cpp b/src/statfs.cpp index 7a68ef19..ce414d5d 100644 --- a/src/statfs.cpp +++ b/src/statfs.cpp @@ -14,14 +14,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include - -#include -#include -#include -#include -#include - #include "config.hpp" #include "errno.hpp" #include "fs_base_stat.hpp" @@ -31,6 +23,14 @@ #include "statvfs_util.hpp" #include "ugid.hpp" +#include + +#include +#include +#include +#include +#include + using std::string; using std::map; using std::vector; @@ -108,7 +108,7 @@ _statfs(const Branches &branches_, if(rv == -1) continue; - rv = fs::lstatvfs(fullpath,stvfs); + rv = fs::lstatvfs(fullpath,&stvfs); if(rv == -1) continue; diff --git a/src/statvfs_util.hpp b/src/statvfs_util.hpp index cccdcba7..092a5646 100644 --- a/src/statvfs_util.hpp +++ b/src/statvfs_util.hpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2016, Antonio SJ Musumeci + Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -16,10 +16,11 @@ #pragma once -#include - #include +#include +#include + namespace StatVFS { static