Browse Source

Add ability to proxy ioprio of client apps (#1534)

pull/1535/head
trapexit 1 week ago
committed by GitHub
parent
commit
569a151da2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 12
      mkdocs/docs/config/func_readdir.md
  2. 2
      mkdocs/docs/config/options.md
  3. 53
      mkdocs/docs/config/proxy-ioprio.md
  4. 1
      mkdocs/mkdocs.yml
  5. 2
      src/config.cpp
  6. 2
      src/config.hpp
  7. 33
      src/config_proxy_ioprio.cpp
  8. 13
      src/config_proxy_ioprio.hpp
  9. 2
      src/error.hpp
  10. 2
      src/fuse_read.cpp
  11. 3
      src/fuse_write.cpp
  12. 73
      src/ioprio.cpp
  13. 20
      src/ioprio.hpp

12
mkdocs/docs/config/func_readdir.md

@ -29,3 +29,15 @@ of the `readdir` call. In v6.16 that was changed to be able to grow to
the [fuse_msg_size](fuse_msg_size.md) which allows a lot more data to
be sent per request and therefore much better performance in certain
situations.
## Technical Details
* On Linux
[getdents](https://man7.org/linux/man-pages/man2/getdents.2.html) is
used instead of
[readdir](https://man7.org/linux/man-pages/man2/readdir.2.html) in
order to leverage larger buffers for better performance on larger
directory reads as well as have more control in concurrent policies.
* [readdir](https://man7.org/linux/man-pages/man2/readdir.2.html) is
used on FreeBSD.

2
mkdocs/docs/config/options.md

@ -162,6 +162,8 @@ config file.
* **scheduling-priority=INT**: Set mergerfs' scheduling
priority. Valid values range from -20 to 19. See `setpriority` man
page for more details. (default: -10)
* **[proxy-ioprio](proxy-ioprio.md)=BOOL**: Use ioprio value from
calling threads when reading and writing. (default: false)
* **fsname=STR**: Sets the name of the filesystem as seen in
**mount**, **df**, etc. Defaults to a list of the source paths
concatenated together with the longest common prefix removed.

53
mkdocs/docs/config/proxy-ioprio.md

@ -0,0 +1,53 @@
# proxy-ioprio
* type: `BOOL`
* default: `false`
* example: `proxy-ioprio=true`
In Linux certain process schedulers have the ability to
[prioritize](https://man7.org/linux/man-pages/man2/ioprio_set.2.html)
based on a per thread [IO
priority](https://www.kernel.org/doc/Documentation/block/ioprio.txt)
assigned to the software by users using tools such as
[ionice](https://man7.org/linux/man-pages/man1/ionice.1.html). Since
such details are not provided by the FUSE protocol, users may not use
schedulers which support IO priority, and the extra overhead to query
the priority and set it within mergerfs it does not by default attempt
to mirror/proxy the value. When enabled however, for all read and
write requests, mergerfs will query the priority of the requesting
process/thread and set the same value on the thread within mergerfs
making the read/write.
This may be useful in situation where the system has high IO load or
processes are known to have varying priorities. See the [man
page](https://man7.org/linux/man-pages/man2/ioprio_set.2.html) for
specific details.
Keep in mind that if no IO scheduler has been set for a thread then by
default the IO priority will match the CPU nice value which for
mergerfs is set by [scheduling-priority](options.md). [Nice
value](https://man7.org/linux/man-pages/man2/getpriority.2.html)
proxying may be added in a future release.
## Conflicts With Other Options
If using [IO passthrough](passthrough.md) there is no reason to set
this value to `true`. However, since `passthrough` leads to mergerfs
IO calls being bypassed entirely will have no impact on performance or
behavior regardless.
When using `passthrough` the IO priority of the client app would be
used directly given the IO is passed through.
## Supported Platforms
Only Linux. This is not supported on FreeBSD.
## Performance Impact
Despite the additional syscalls requires the impact appears
undetectable with larger buffer sizes. At very small buffer sizes
(such as 512 bytes) there is a noticable but small impact (~1.5%).

1
mkdocs/mkdocs.yml

@ -81,6 +81,7 @@ nav:
- config/inodecalc.md
- config/threads.md
- config/pin-threads.md
- config/proxy-ioprio.md
- config/link_cow.md
- config/fuse_msg_size.md
- config/follow-symlinks.md

2
src/config.cpp

@ -119,6 +119,7 @@ Config::Config()
nullrw(false),
parallel_direct_writes(true),
posix_acl(false),
proxy_ioprio(false),
readahead(0),
readdir("seq"),
readdirplus(false),
@ -207,6 +208,7 @@ Config::Config()
_map["parallel-direct-writes"] = &parallel_direct_writes;
_map["pin-threads"] = &fuse_pin_threads;
_map["posix_acl"] = &posix_acl;
_map["proxy-ioprio"] = &proxy_ioprio;
_map["readahead"] = &readahead;
_map["readdirplus"] = &readdirplus;
_map["rename-exdev"] = &rename_exdev;

2
src/config.hpp

@ -30,6 +30,7 @@
#include "config_pagesize.hpp"
#include "config_passthrough.hpp"
#include "config_pid.hpp"
#include "config_proxy_ioprio.hpp"
#include "config_rename_exdev.hpp"
#include "config_set.hpp"
#include "config_statfs.hpp"
@ -126,6 +127,7 @@ public:
ConfigBOOL parallel_direct_writes;
ConfigGetPid pid;
ConfigBOOL posix_acl;
ProxyIOPrio proxy_ioprio;
ConfigUINT64 readahead;
FUSE::ReadDir readdir;
ConfigBOOL readdirplus;

33
src/config_proxy_ioprio.cpp

@ -0,0 +1,33 @@
#include "config_proxy_ioprio.hpp"
#include "ioprio.hpp"
#include "from_string.hpp"
ProxyIOPrio::ProxyIOPrio(const bool b_)
{
ioprio::enable(b_);
}
std::string
ProxyIOPrio::to_string(void) const
{
if(ioprio::enabled())
return "true";
return "false";
}
int
ProxyIOPrio::from_string(const std::string_view s_)
{
int rv;
bool enable;
rv = str::from(s_,&enable);
if(rv)
return rv;
ioprio::enable(enable);
return 0;
}

13
src/config_proxy_ioprio.hpp

@ -0,0 +1,13 @@
#pragma once
#include "tofrom_string.hpp"
class ProxyIOPrio : public ToFromString
{
public:
ProxyIOPrio(const bool);
public:
std::string to_string(void) const final;
int from_string(const std::string_view) final;
};

2
src/error.hpp

@ -24,7 +24,7 @@ public:
operator=(int v_)
{
if(!_err.has_value())
_err = v_;
_err = ((v_ >= 0) ? 0 : v_);
else if(v_ >= 0)
_err = 0;

2
src/fuse_read.cpp

@ -19,6 +19,7 @@
#include "errno.hpp"
#include "fileinfo.hpp"
#include "fs_pread.hpp"
#include "ioprio.hpp"
#include "fuse.h"
@ -60,6 +61,7 @@ FUSE::read(const fuse_file_info_t *ffi_,
size_t size_,
off_t offset_)
{
ioprio::SetFrom iop(fuse_get_context()->pid);
FileInfo *fi = FileInfo::from_fh(ffi_->fh);
if(fi->direct_io)

3
src/fuse_write.cpp

@ -24,6 +24,7 @@
#include "fs_movefile_and_open.hpp"
#include "fs_pwrite.hpp"
#include "fs_pwriten.hpp"
#include "ioprio.hpp"
#include "fuse.h"
@ -187,6 +188,8 @@ FUSE::write(const fuse_file_info_t *ffi_,
size_t count_,
off_t offset_)
{
ioprio::SetFrom iop(fuse_get_context()->pid);
return ::_write(ffi_,buf_,count_,offset_);
}

73
src/ioprio.cpp

@ -0,0 +1,73 @@
#include "ioprio.hpp"
#ifdef __linux__
# include <linux/ioprio.h>
# include <sys/syscall.h>
# include <unistd.h>
# include <errno.h>
#else
#warning "ioprio not supported on this platform"
#endif
thread_local int ioprio::SetFrom::thread_prio = -1;
bool _enabled = false;
int
ioprio::get(const int which_,
const int who_)
{
#ifdef SYS_ioprio_get
int rv;
rv = syscall(SYS_ioprio_get,which_,who_);
return ((rv == -1) ? -errno : rv);
#else
return -ENOSUP;
#endif
}
int
ioprio::set(const int which_,
const int who_,
const int ioprio_)
{
#ifdef SYS_ioprio_set
int rv;
rv = syscall(SYS_ioprio_set,which_,who_,ioprio_);
return ((rv == -1) ? -errno : rv);
#else
return -ENOSUP;
#endif
}
void
ioprio::enable(const bool enable_)
{
_enabled = enable_;
}
bool
ioprio::enabled()
{
return _enabled;
}
ioprio::SetFrom::SetFrom(const pid_t pid_)
{
int client_prio;
if(!_enabled)
return;
client_prio = ioprio::get(IOPRIO_WHO_PROCESS,pid_);
if(client_prio < 0)
return;
if(client_prio == thread_prio)
return;
thread_prio = client_prio;
ioprio::set(IOPRIO_WHO_PROCESS,0,client_prio);
}

20
src/ioprio.hpp

@ -0,0 +1,20 @@
#pragma once
#include <sys/types.h>
namespace ioprio
{
void enable(const bool);
bool enabled();
int get(const int which, const int who);
int set(const int which, const int who, const int ioprio);
struct SetFrom
{
static thread_local int thread_prio;
SetFrom(const pid_t pid);
};
};
Loading…
Cancel
Save