add statfs cache

This commit is contained in:
Antonio SJ Musumeci 2018-12-12 10:08:17 -05:00
parent f42bd8666e
commit 5be7e007ce
22 changed files with 303 additions and 95 deletions

View File

@ -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=<int>**: 'open' policy cache timeout in seconds. (default: 0)
* **cache.statfs=<int>**: '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.

View File

@ -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

View File

@ -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<string> &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)

View File

@ -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<string> &basepaths,
const char *fusepath,
vector<string> &paths);

View File

@ -1,7 +1,7 @@
/*
ISC License
Copyright (c) 2016, Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2019, Antonio SJ Musumeci <trapexit@spawn.link>
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;

View File

@ -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 <stdint.h>
@ -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);

145
src/fs_statvfs_cache.cpp Normal file
View File

@ -0,0 +1,145 @@
/*
ISC License
Copyright (c) 2016, Antonio SJ Musumeci <trapexit@spawn.link>
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 <map>
#include <string>
#include <pthread.h>
#include <stdint.h>
#include <sys/statvfs.h>
#include <sys/time.h>
struct Element
{
uint64_t time;
struct statvfs st;
};
typedef std::map<std::string,Element> 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;
}
}

44
src/fs_statvfs_cache.hpp Normal file
View File

@ -0,0 +1,44 @@
/*
ISC License
Copyright (c) 2019, Antonio SJ Musumeci <trapexit@spawn.link>
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 <stdint.h>
#include <sys/statvfs.h>
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_);
}

View File

@ -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;
}

View File

@ -45,6 +45,7 @@ _listxattr_controlfile(char *list,
buildvector<string>
("user.mergerfs.branches")
("user.mergerfs.cache.open")
("user.mergerfs.cache.statfs")
("user.mergerfs.direct_io")
("user.mergerfs.dropcacheonclose")
("user.mergerfs.ignorepponrename")

View File

@ -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.<c>=<p> Set functions in category <c> to <p>\n"
" -o cache.open=<int> 'open' policy cache timeout in seconds.\n"
" default = 0 (disabled)\n"
" -o cache.statfs=<int> '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"

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -58,7 +58,8 @@ namespace mergerfs
const Config &config = Config::get();
FileInfo *fi = reinterpret_cast<FileInfo*>(ffi_->fh);
config.open_cache.cleanup(10);
if(config.open_cache.timeout)
config.open_cache.cleanup(10);
return local::release(fi,config.dropcacheonclose);
}

View File

@ -39,10 +39,7 @@ namespace mergerfs
releasedir(const char *fusepath_,
fuse_file_info *ffi_)
{
const Config &config = Config::get();
DirInfo *di = reinterpret_cast<DirInfo*>(ffi_->fh);
config.open_cache.cleanup(10);
DirInfo *di = reinterpret_cast<DirInfo*>(ffi_->fh);
return local::releasedir(di);
}

View File

@ -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:

View File

@ -14,14 +14,6 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <fuse.h>
#include <algorithm>
#include <limits>
#include <map>
#include <string>
#include <vector>
#include "config.hpp"
#include "errno.hpp"
#include "fs_base_stat.hpp"
@ -31,6 +23,14 @@
#include "statvfs_util.hpp"
#include "ugid.hpp"
#include <fuse.h>
#include <algorithm>
#include <limits>
#include <map>
#include <string>
#include <vector>
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;

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 2016, Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2019, Antonio SJ Musumeci <trapexit@spawn.link>
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 <sys/statvfs.h>
#include <string>
#include <stdint.h>
#include <sys/statvfs.h>
namespace StatVFS
{
static