Preliminary advise support

This commit is contained in:
Andrea Mazzoleni 2016-05-15 14:18:44 +02:00
parent 32a57a5808
commit 21241c5078
23 changed files with 664 additions and 141 deletions

View file

@ -13,6 +13,15 @@ SnapRAID HISTORY
file patterns containing real characters matching the globbing '*?[]'
ones. In Unix it was already possible to do the same escaping with
'\'.
* Added test options for selecting the file advise mode to use:
--test-io-advise-none for standard mode
--test-io-advise-sequential advise sequential operations (Linux/Windows)
--test-io-advise-flush flush data after every operation (Linux)
--test-io-advise-discard discard cache after every operation (Linux)
--test-io-advise-discard-2 discard cache every 2 MB (Linux)
--test-io-advise-discard-8 discard cache every 8 MB (Linux)
--test-io-advise-discard-32 discard cache every 32 MB (Linux)
--test-io-advise-direct use direct mode (Linux/Windows)
* Fixed running in Windows platforms that miss the RtlGenRandom
function.
* Added a new 'hashsize' configuration option. It could be useful in

View file

@ -436,7 +436,10 @@ endif
$(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS_VERBOSE) -c $(PAR1) --test-expect-need-sync diff > output.log
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-fallocate -c $(PAR1) sync
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) status
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-sequential -c $(PAR1) check
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-io-advise-none -c $(PAR1) sync -F
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-io-advise-sequential -c $(PAR1) sync -F
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-io-advise-flush -c $(PAR1) sync -F
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-io-advise-discard -c $(PAR1) sync -F
#### CHANGE LINKS ####
# Use a different size ("22" instead of "1") to ensure to recognize the file different
# even if it gets the same timestamp in case subsecond timestamp is no available

View file

@ -34,6 +34,7 @@ int handle_create(struct snapraid_handle* handle, struct snapraid_file* file, in
return 0;
}
advise_init(&handle->advise, mode);
pathprint(handle->path, sizeof(handle->path), "%s%s", handle->disk->dir, file->sub);
ret = mkancestor(handle->path);
@ -49,10 +50,7 @@ int handle_create(struct snapraid_handle* handle, struct snapraid_file* file, in
/* flags for opening */
/* O_BINARY: open as binary file (Windows only) */
/* O_NOFOLLOW: do not follow links to ensure to open the real file */
/* O_SEQUENTIAL: improve performance for sequential access (Windows only) */
flags = O_BINARY | O_NOFOLLOW;
if ((mode & MODE_SEQUENTIAL) != 0)
flags |= O_SEQUENTIAL;
flags = O_BINARY | O_NOFOLLOW | advise_flags(&handle->advise);
/* open for read write */
handle->f = open(handle->path, flags | O_RDWR);
@ -110,18 +108,13 @@ int handle_create(struct snapraid_handle* handle, struct snapraid_file* file, in
/* get the size of the existing data */
handle->valid_size = handle->st.st_size;
#if HAVE_POSIX_FADVISE
if ((mode & MODE_SEQUENTIAL) != 0) {
/* advise sequential access */
ret = posix_fadvise(handle->f, 0, 0, POSIX_FADV_SEQUENTIAL);
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal("Error advising file '%s'. %s.\n", handle->path, strerror(ret));
return -1;
/* LCOV_EXCL_STOP */
}
ret = advise_open(&handle->advise, handle->f);
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal("Error advising file '%s'. %s.\n", handle->path, strerror(errno));
return -1;
/* LCOV_EXCL_STOP */
}
#endif
return 0;
}
@ -161,6 +154,7 @@ int handle_open(struct snapraid_handle* handle, struct snapraid_file* file, int
return 0;
}
advise_init(&handle->advise, mode);
pathprint(handle->path, sizeof(handle->path), "%s%s", handle->disk->dir, file->sub);
/* for sure not created */
@ -169,10 +163,7 @@ int handle_open(struct snapraid_handle* handle, struct snapraid_file* file, int
/* flags for opening */
/* O_BINARY: open as binary file (Windows only) */
/* O_NOFOLLOW: do not follow links to ensure to open the real file */
/* O_SEQUENTIAL: improve performance for sequential access (Windows only) */
flags = O_BINARY | O_NOFOLLOW;
if ((mode & MODE_SEQUENTIAL) != 0)
flags |= O_SEQUENTIAL;
flags = O_BINARY | O_NOFOLLOW | advise_flags(&handle->advise);
/* open for read */
handle->f = open_noatime(handle->path, flags | O_RDONLY);
@ -204,18 +195,13 @@ int handle_open(struct snapraid_handle* handle, struct snapraid_file* file, int
/* get the size of the existing data */
handle->valid_size = handle->st.st_size;
#if HAVE_POSIX_FADVISE
if ((mode & MODE_SEQUENTIAL) != 0) {
/* advise sequential access */
ret = posix_fadvise(handle->f, 0, 0, POSIX_FADV_SEQUENTIAL);
if (ret != 0) {
/* LCOV_EXCL_START */
out("Error advising file '%s'. %s.\n", handle->path, strerror(ret));
return -1;
/* LCOV_EXCL_STOP */
}
ret = advise_open(&handle->advise, handle->f);
if (ret != 0) {
/* LCOV_EXCL_START */
out("Error advising file '%s'. %s.\n", handle->path, strerror(errno));
return -1;
/* LCOV_EXCL_STOP */
}
#endif
return 0;
}
@ -254,6 +240,7 @@ int handle_read(struct snapraid_handle* handle, block_off_t file_pos, unsigned c
data_off_t offset;
unsigned read_size;
unsigned count;
int ret;
offset = file_pos * (data_off_t)block_size;
@ -274,10 +261,11 @@ int handle_read(struct snapraid_handle* handle, block_off_t file_pos, unsigned c
count = 0;
do {
read_ret = pread(handle->f, block_buffer + count, read_size - count, offset + count);
/* read the full block to support O_DIRECT */
read_ret = pread(handle->f, block_buffer + count, block_size - count, offset + count);
if (read_ret < 0) {
/* LCOV_EXCL_START */
out("Error reading file '%s' at offset %" PRIu64 " for size %u. %s.\n", handle->path, offset + count, read_size - count, strerror(errno));
out("Error reading file '%s' at offset %" PRIu64 " for size %u. %s.\n", handle->path, offset + count, block_size - count, strerror(errno));
return -1;
/* LCOV_EXCL_STOP */
}
@ -294,16 +282,13 @@ int handle_read(struct snapraid_handle* handle, block_off_t file_pos, unsigned c
memset(block_buffer + read_size, 0, block_size - read_size);
}
/* Here isn't needed to call posix_fadvise(..., POSIX_FADV_DONTNEED) */
/* because we already advised sequential access with POSIX_FADV_SEQUENTIAL. */
/* In Linux 2.6.33 it's enough to ensure that data is not kept in the cache. */
/* Better to do nothing and save a syscall for each block. */
/* Here we cannot call posix_fadvise(..., POSIX_FADV_WILLNEED) for the next block */
/* because it may be blocking */
/* See Ted Ts'o comment in "posix_fadvise(POSIX_FADV_WILLNEED) waits before returning?" */
/* at: https://lkml.org/lkml/2010/12/6/122 */
/* We relay only on the automatic readahead triggered by POSIX_FADV_SEQUENTIAL. */
ret = advise_read(&handle->advise, handle->f, offset, block_size);
if (ret != 0) {
/* LCOV_EXCL_START */
out("Error advising file '%s'. %s.\n", handle->path, strerror(errno));
return -1;
/* LCOV_EXCL_STOP */
}
return read_size;
}
@ -313,6 +298,7 @@ int handle_write(struct snapraid_handle* handle, block_off_t file_pos, unsigned
ssize_t write_ret;
data_off_t offset;
unsigned write_size;
int ret;
offset = file_pos * (data_off_t)block_size;
@ -331,11 +317,13 @@ int handle_write(struct snapraid_handle* handle, block_off_t file_pos, unsigned
handle->valid_size = offset + write_size;
}
/* Here doesn't make sense to call posix_fadvise(..., POSIX_FADV_DONTNEED) because */
/* at this time the data is still not yet written and it cannot be discharged */
/* from the system cache. */
/* This is at least true for Linux 2.6.21 that just ignores POSIX_FADV_DONTNEED */
/* if the data is not yet written. */
ret = advise_write(&handle->advise, handle->f, offset, block_size);
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal("Error advising file '%s'. %s.\n", handle->path, strerror(errno));
return -1;
/* LCOV_EXCL_STOP */
}
return 0;
}

View file

@ -30,6 +30,7 @@ struct snapraid_handle {
struct snapraid_file* file; /**< File opened. When the file is closed, it's set to 0. */
int f; /**< Handle of the file. */
struct stat st; /**< Stat info of the opened file. */
struct advise_struct advise; /**< Advise information. */
data_off_t valid_size; /**< Size of the valid data. */
int created; /**< If the file was created, otherwise it was already existing. */
};

View file

@ -62,6 +62,7 @@ static void import_file(struct snapraid_state* state, const char* path, uint64_t
int f;
int flags;
unsigned block_size = state->block_size;
struct advise_struct advise;
file = malloc_nofail(sizeof(struct snapraid_import_file));
file->path = strdup_nofail(path);
@ -71,11 +72,10 @@ static void import_file(struct snapraid_state* state, const char* path, uint64_t
buffer = malloc_nofail(block_size);
advise_init(&advise, state->file_mode);
/* open for read */
/* O_SEQUENTIAL: opening in sequential mode in Windows */
flags = O_RDONLY | O_BINARY;
if ((state->file_mode & MODE_SEQUENTIAL) != 0)
flags |= O_SEQUENTIAL;
flags = O_RDONLY | O_BINARY | advise_flags(&advise);
f = open(path, flags);
if (f == -1) {
/* LCOV_EXCL_START */
@ -84,18 +84,13 @@ static void import_file(struct snapraid_state* state, const char* path, uint64_t
/* LCOV_EXCL_STOP */
}
#if HAVE_POSIX_FADVISE
if ((state->file_mode & MODE_SEQUENTIAL) != 0) {
/* advise sequential access */
ret = posix_fadvise(f, 0, 0, POSIX_FADV_SEQUENTIAL);
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal("Error advising file '%s'. %s.\n", path, strerror(ret));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = advise_open(&advise, f);
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal("Error advising file '%s'. %s.\n", path, strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
#endif
offset = 0;
for (i = 0; i < file->blockmax; ++i) {

View file

@ -880,7 +880,10 @@ void io_init(struct snapraid_io* io, struct snapraid_state* state,
io->buffer_max = buffer_max;
allocated = 0;
for (i = 0; i < io->io_max; ++i) {
io->buffer_map[i] = malloc_nofail_vector_align(handle_max, buffer_max, state->block_size, &io->buffer_alloc_map[i]);
if (state->file_mode != ADVISE_DIRECT)
io->buffer_map[i] = malloc_nofail_vector_align(handle_max, buffer_max, state->block_size, &io->buffer_alloc_map[i]);
else
io->buffer_map[i] = malloc_nofail_vector_direct(handle_max, buffer_max, state->block_size, &io->buffer_alloc_map[i]);
if (!state->opt.skip_self)
mtest_vector(io->buffer_max, state->block_size, io->buffer_map[i]);
allocated += state->block_size * buffer_max;

View file

@ -602,6 +602,9 @@ static void windows_errno(DWORD error)
/* Other cases are here identified with EINVAL */
errno = EINVAL;
break;
case ERROR_HANDLE_EOF : /* in ReadFile() over the end of the file */
errno = EINVAL;
break;
case ERROR_FILE_NOT_FOUND :
case ERROR_PATH_NOT_FOUND : /* in GetFileAttributeW() if internal path not found */
errno = ENOENT;
@ -1056,17 +1059,82 @@ FILE* windows_fopen(const char* file, const char* mode)
int windows_open(const char* file, int flags, ...)
{
wchar_t conv_buf[CONV_MAX];
va_list args;
int ret;
HANDLE h;
int f;
DWORD access;
DWORD share;
DWORD create;
DWORD attr;
va_start(args, flags);
if ((flags & O_CREAT) != 0)
ret = _wopen(convert(conv_buf, file), flags, va_arg(args, int));
else
ret = _wopen(convert(conv_buf, file), flags);
va_end(args);
switch (flags & O_ACCMODE) {
case O_RDONLY :
access = GENERIC_READ;
break;
case O_WRONLY :
access = GENERIC_WRITE;
break;
case O_RDWR :
access = GENERIC_READ | GENERIC_WRITE;
break;
default:
errno = EINVAL;
return -1;
}
return ret;
share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
switch (flags & (O_CREAT | O_EXCL | O_TRUNC)) {
case 0 :
create = OPEN_EXISTING;
break;
case O_CREAT :
create = OPEN_ALWAYS;
break;
case O_CREAT | O_EXCL :
case O_CREAT | O_EXCL | O_TRUNC :
create = CREATE_NEW;
break;
case O_CREAT | O_TRUNC :
create = CREATE_ALWAYS;
break;
case O_TRUNC :
create = TRUNCATE_EXISTING;
break;
default:
errno = EINVAL;
return -1;
}
attr = FILE_ATTRIBUTE_NORMAL;
if ((flags & O_DIRECT) != 0)
attr |= FILE_FLAG_NO_BUFFERING;
if ((flags & O_DSYNC) != 0)
attr |= FILE_FLAG_WRITE_THROUGH;
if ((flags & O_RANDOM) != 0)
attr |= FILE_FLAG_RANDOM_ACCESS;
if ((flags & O_SEQUENTIAL) != 0)
attr |= FILE_FLAG_SEQUENTIAL_SCAN;
if ((flags & _O_SHORT_LIVED) != 0)
attr |= FILE_ATTRIBUTE_TEMPORARY;
if ((flags & O_TEMPORARY) != 0)
attr |= FILE_FLAG_DELETE_ON_CLOSE;
h = CreateFileW(convert(conv_buf, file), access, share, 0, create, attr, 0);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
}
/* mask out flags unknown by Windows */
flags &= ~(O_DIRECT | O_DSYNC);
f = _open_osfhandle((intptr_t)h, flags);
if (f == -1) {
CloseHandle(h);
return -1;
}
return f;
}
struct windows_dir_struct {
@ -1785,6 +1853,22 @@ retry:
return count;
}
size_t windows_direct_size(void)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
/*
* MSDN 'File Buffering'
* https://msdn.microsoft.com/en-us/library/windows/desktop/cc644950%28v=vs.85%29.aspx
*
* "Therefore, in most situations, page-aligned memory will also be sector-aligned,"
* "because the case where the sector size is larger than the page size is rare."
*/
return si.dwPageSize;
}
uint64_t tick(void)
{
LARGE_INTEGER t;

View file

@ -119,6 +119,10 @@
#define pread windows_pread
#undef pwrite
#define pwrite windows_pwrite
#define direct_size windows_direct_size
#define HAVE_DIRECT_IO 1
#define O_DIRECT 0x10000000
#define O_DSYNC 0x20000000
/**
* If nanoseconds are not supported, we report the special STAT_NSEC_INVALID value,
@ -362,6 +366,11 @@ ssize_t windows_pread(int f, void* buffer, size_t size, off_t offset);
*/
ssize_t windows_pwrite(int f, const void* buffer, size_t size, off_t offset);
/**
* List direct_size().
*/
size_t windows_direct_size(void);
/****************************************************************************/
/* thread */

View file

@ -190,17 +190,14 @@ int parity_create(struct snapraid_parity_handle* handle, const struct snapraid_p
int ret;
int flags;
/* mark it as initialized */
advise_init(&split->advise, mode);
pathcpy(split->path, sizeof(split->path), parity->split_map[s].path);
split->f = -1;
split->size = parity->split_map[s].size;
++handle->split_mac;
/* opening in sequential mode in Windows */
/* O_SEQUENTIAL: opening in sequential mode in Windows */
flags = O_RDWR | O_CREAT | O_BINARY;
if ((mode & MODE_SEQUENTIAL) != 0)
flags |= O_SEQUENTIAL;
flags = O_RDWR | O_CREAT | O_BINARY | advise_flags(&split->advise);
split->f = open(split->path, flags, 0600);
if (split->f == -1) {
/* LCOV_EXCL_START */
@ -225,18 +222,13 @@ int parity_create(struct snapraid_parity_handle* handle, const struct snapraid_p
if (split->size == PARITY_SIZE_INVALID)
split->size = split->st.st_size;
#if HAVE_POSIX_FADVISE
if ((mode & MODE_SEQUENTIAL) != 0) {
/* advise sequential access */
ret = posix_fadvise(split->f, 0, 0, POSIX_FADV_SEQUENTIAL);
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal("Error advising parity file '%s'. %s.\n", split->path, strerror(ret));
goto bail;
/* LCOV_EXCL_STOP */
}
ret = advise_open(&split->advise, split->f);
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal("Error advising parity file '%s'. %s.\n", split->path, strerror(errno));
goto bail;
/* LCOV_EXCL_STOP */
}
#endif
}
return 0;
@ -520,18 +512,16 @@ int parity_open(struct snapraid_parity_handle* handle, const struct snapraid_par
int ret;
int flags;
/* mark it as initialized */
advise_init(&split->advise, mode);
pathcpy(split->path, sizeof(split->path), parity->split_map[s].path);
split->f = -1;
split->size = parity->split_map[s].size;
++handle->split_mac;
/* open for read */
/* O_SEQUENTIAL: opening in sequential mode in Windows */
/* O_NOATIME: do not change access time */
flags = O_RDONLY | O_BINARY;
if ((mode & MODE_SEQUENTIAL) != 0)
flags |= O_SEQUENTIAL;
flags = O_RDONLY | O_BINARY | advise_flags(&split->advise);
split->f = open_noatime(split->path, flags);
if (split->f == -1) {
log_fatal("Error opening parity file '%s'. %s.\n", split->path, strerror(errno));
@ -554,18 +544,13 @@ int parity_open(struct snapraid_parity_handle* handle, const struct snapraid_par
if (split->size == PARITY_SIZE_INVALID)
split->size = split->st.st_size;
#if HAVE_POSIX_FADVISE
if ((mode & MODE_SEQUENTIAL) != 0) {
/* advise sequential access */
ret = posix_fadvise(split->f, 0, 0, POSIX_FADV_SEQUENTIAL);
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal("Error advising parity file '%s'. %s.\n", split->path, strerror(ret));
goto bail;
/* LCOV_EXCL_STOP */
}
ret = advise_open(&split->advise, split->f);
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal("Error advising parity file '%s'. %s.\n", split->path, strerror(errno));
goto bail;
/* LCOV_EXCL_STOP */
}
#endif
}
return 0;
@ -659,6 +644,7 @@ int parity_write(struct snapraid_parity_handle* handle, block_off_t pos, unsigne
ssize_t write_ret;
data_off_t offset;
struct snapraid_split_handle* split;
int ret;
offset = pos * (data_off_t)block_size;
@ -682,8 +668,13 @@ int parity_write(struct snapraid_parity_handle* handle, block_off_t pos, unsigne
/* LCOV_EXCL_STOP */
}
/* Here doesn't make sense to call posix_fadvise(..., POSIX_FADV_DONTNEED) because */
/* at this time the data is still in not yet written and it cannot be discharged. */
ret = advise_write(&split->advise, split->f, offset, block_size);
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal("Error advising parity file '%s'. %s.\n", split->path, strerror(errno));
return -1;
/* LCOV_EXCL_STOP */
}
return 0;
}
@ -694,6 +685,7 @@ int parity_read(struct snapraid_parity_handle* handle, block_off_t pos, unsigned
data_off_t offset;
unsigned count;
struct snapraid_split_handle* split;
int ret;
offset = pos * (data_off_t)block_size;
@ -724,10 +716,13 @@ int parity_read(struct snapraid_parity_handle* handle, block_off_t pos, unsigned
count += read_ret;
} while (count < block_size);
/* Here isn't needed to call posix_fadvise(..., POSIX_FADV_DONTNEED) */
/* because we already advised sequential access with POSIX_FADV_SEQUENTIAL. */
/* In Linux 2.6.33 it's enough to ensure that data is not kept in the cache. */
/* Better to do nothing and save a syscall for each block. */
ret = advise_read(&split->advise, split->f, offset, block_size);
if (ret != 0) {
/* LCOV_EXCL_START */
out("Error advising parity file '%s'. %s.\n", split->path, strerror(errno));
return -1;
/* LCOV_EXCL_STOP */
}
return block_size;
}

View file

@ -27,6 +27,7 @@ struct snapraid_split_handle {
char path[PATH_MAX]; /**< Path of the file. */
int f; /**< Handle of the files. */
struct stat st; /**< Stat info of the opened file. */
struct advise_struct advise; /**< Advise information. */
/**
* Size of the parity split.

View file

@ -254,7 +254,6 @@ void config(char* conf, size_t conf_size, const char* argv0)
#define OPT_TEST_SKIP_SIGN 260
#define OPT_TEST_SKIP_FALLOCATE 261
#define OPT_TEST_SKIP_DEVICE 262
#define OPT_TEST_SKIP_SEQUENTIAL 263
#define OPT_TEST_FORCE_MURMUR3 264
#define OPT_TEST_FORCE_SPOOKY2 265
#define OPT_TEST_SKIP_LOCK 266
@ -283,6 +282,14 @@ void config(char* conf, size_t conf_size, const char* argv0)
#define OPT_TEST_IO_CACHE 290
#define OPT_TEST_IO_STATS 291
#define OPT_TEST_COND_SIGNAL_OUTSIDE 292
#define OPT_TEST_IO_ADVISE_NONE 293
#define OPT_TEST_IO_ADVISE_SEQUENTIAL 294
#define OPT_TEST_IO_ADVISE_FLUSH 295
#define OPT_TEST_IO_ADVISE_DISCARD 296
#define OPT_TEST_IO_ADVISE_DISCARD_2 297
#define OPT_TEST_IO_ADVISE_DISCARD_8 298
#define OPT_TEST_IO_ADVISE_DISCARD_32 299
#define OPT_TEST_IO_ADVISE_DIRECT 300
#if HAVE_GETOPT_LONG
struct option long_options[] = {
@ -335,9 +342,6 @@ struct option long_options[] = {
/* Skip the fallocate() when growing the parity files */
{ "test-skip-fallocate", 0, 0, OPT_TEST_SKIP_FALLOCATE },
/* Skip the sequential hint when reading files */
{ "test-skip-sequential", 0, 0, OPT_TEST_SKIP_SEQUENTIAL },
/* Skip the device check */
{ "test-skip-device", 0, 0, OPT_TEST_SKIP_DEVICE },
@ -419,6 +423,30 @@ struct option long_options[] = {
/* Signal condition variable outside the mutex */
{ "test-cond-signal-outside", 0, 0, OPT_TEST_COND_SIGNAL_OUTSIDE },
/* Set the io advise to none */
{ "test-io-advise-none", 0, 0, OPT_TEST_IO_ADVISE_NONE },
/* Set the io advise to sequential */
{ "test-io-advise-sequential", 0, 0, OPT_TEST_IO_ADVISE_SEQUENTIAL },
/* Set the io advise to flush */
{ "test-io-advise-flush", 0, 0, OPT_TEST_IO_ADVISE_FLUSH },
/* Set the io advise to discard */
{ "test-io-advise-discard", 0, 0, OPT_TEST_IO_ADVISE_DISCARD },
/* Set the io advise to discard */
{ "test-io-advise-discard-2", 0, 0, OPT_TEST_IO_ADVISE_DISCARD_2 },
/* Set the io advise to discard */
{ "test-io-advise-discard-8", 0, 0, OPT_TEST_IO_ADVISE_DISCARD_8 },
/* Set the io advise to discard */
{ "test-io-advise-discard-32", 0, 0, OPT_TEST_IO_ADVISE_DISCARD_32 },
/* Set the io advise to direct */
{ "test-io-advise-direct", 0, 0, OPT_TEST_IO_ADVISE_DIRECT },
{ 0, 0, 0, 0 }
};
#endif
@ -718,9 +746,6 @@ int main(int argc, char* argv[])
case OPT_TEST_SKIP_FALLOCATE :
opt.skip_fallocate = 1;
break;
case OPT_TEST_SKIP_SEQUENTIAL :
opt.skip_sequential = 1;
break;
case OPT_TEST_SKIP_DEVICE :
opt.skip_device = 1;
period = 50; /* reduce period of the speed test */
@ -818,6 +843,30 @@ int main(int argc, char* argv[])
thread_cond_signal_outside = 1;
#endif
break;
case OPT_TEST_IO_ADVISE_NONE :
opt.file_mode = ADVISE_NONE;
break;
case OPT_TEST_IO_ADVISE_SEQUENTIAL :
opt.file_mode = ADVISE_SEQUENTIAL;
break;
case OPT_TEST_IO_ADVISE_FLUSH :
opt.file_mode = ADVISE_FLUSH;
break;
case OPT_TEST_IO_ADVISE_DISCARD :
opt.file_mode = ADVISE_DISCARD;
break;
case OPT_TEST_IO_ADVISE_DISCARD_2 :
opt.file_mode = ADVISE_DISCARD_2;
break;
case OPT_TEST_IO_ADVISE_DISCARD_8 :
opt.file_mode = ADVISE_DISCARD_8;
break;
case OPT_TEST_IO_ADVISE_DISCARD_32 :
opt.file_mode = ADVISE_DISCARD_32;
break;
case OPT_TEST_IO_ADVISE_DIRECT :
opt.file_mode = ADVISE_DIRECT;
break;
default :
/* LCOV_EXCL_START */
log_fatal("Unknown option '%c'\n", (char)c);
@ -1074,6 +1123,20 @@ int main(int argc, char* argv[])
break;
}
switch (operation) {
#if HAVE_DIRECT_IO
case OPERATION_SYNC :
case OPERATION_SCRUB :
case OPERATION_DRY :
break;
#endif
default:
/* we allow direct IO only on some commands */
if (opt.file_mode == ADVISE_DIRECT)
opt.file_mode = ADVISE_SEQUENTIAL;
break;
}
switch (operation) {
case OPERATION_DEVICES :
case OPERATION_SMART :

View file

@ -147,7 +147,7 @@ void state_init(struct snapraid_state* state)
state->checked_read = 0;
state->block_size = 256 * KIBI; /* default 256 KiB */
state->raid_mode = RAID_MODE_CAUCHY;
state->file_mode = MODE_SEQUENTIAL;
state->file_mode = ADVISE_DEFAULT;
for (l = 0; l < LEV_MAX; ++l) {
state->parity[l].split_mac = 0;
for (s = 0; s < SPLIT_MAX; ++s) {
@ -533,8 +533,12 @@ void state_config(struct snapraid_state* state, const char* path, const char* co
state->opt.force_order = SORT_PHYSICAL;
/* adjust file mode */
if (state->opt.skip_sequential)
state->file_mode &= ~MODE_SEQUENTIAL;
if (state->opt.file_mode != ADVISE_DEFAULT) {
state->file_mode = state->opt.file_mode;
} else {
/* default mode, if nothing is specified */
state->file_mode = ADVISE_SEQUENTIAL;
}
/* store current command */
state->command = command;

View file

@ -56,11 +56,6 @@ const char* lev_config_name(unsigned level);
#define GIBI (1024 * 1024 * 1024)
#define TEBI (1024 * 1024 * 1024 * 1024LL)
/**
* File modes.
*/
#define MODE_SEQUENTIAL 1 /**< Open the file in sequential mode. */
/**
* Global variable to identify if Ctrl+C is pressed.
*/
@ -93,7 +88,7 @@ struct snapraid_option {
int skip_device; /**< Skip devices matching checks. */
int skip_sign; /**< Skip the sign check for content files. */
int skip_fallocate; /**< Skip the use of fallocate(). */
int skip_sequential; /**< Skip sequential hint. */
int file_mode; /**< File mode. Mask of ADVISE_* flags. */
int skip_lock; /**< Skip the lock file protection. */
int skip_self; /**< Skip the self-test. */
int skip_content_check; /**< Relax some content file checks. */
@ -129,7 +124,7 @@ struct snapraid_state {
int checked_read; /**< If the state was read and checked. */
uint32_t block_size; /**< Block size in bytes. */
unsigned raid_mode; /**< Raid mode to use. RAID_MODE_DEFAULT or RAID_MODE_ALTERNATE. */
int file_mode; /**< File access mode. Combination of MODE_* flags. */
int file_mode; /**< File access mode. Combination of ADVISE_* flags. */
struct snapraid_parity parity[LEV_MAX]; /**< Parity vector. */
char share[PATH_MAX]; /**< Path of the share tree. If !=0 pool links are created in a different way. */
char pool[PATH_MAX]; /**< Path of the pool tree. */

View file

@ -879,6 +879,262 @@ int fmtime(int f, int64_t mtime_sec, int mtime_nsec)
return ret;
}
/****************************************************************************/
/* advise */
void advise_init(struct advise_struct* advise, int mode)
{
advise->mode = mode;
advise->dirty_begin = 0;
advise->dirty_end = 0;
}
int advise_flags(struct advise_struct* advise)
{
int flags = 0;
if (advise->mode == ADVISE_SEQUENTIAL
|| advise->mode == ADVISE_FLUSH
|| advise->mode == ADVISE_DISCARD
|| advise->mode == ADVISE_DISCARD_2
|| advise->mode == ADVISE_DISCARD_8
|| advise->mode == ADVISE_DISCARD_32
)
flags |= O_SEQUENTIAL;
#if HAVE_DIRECT_IO
if (advise->mode == ADVISE_DIRECT)
flags |= O_DIRECT;
#endif
return flags;
}
int advise_open(struct advise_struct* advise, int f)
{
(void)advise;
(void)f;
#if HAVE_POSIX_FADVISE
if (advise->mode == ADVISE_SEQUENTIAL
|| advise->mode == ADVISE_FLUSH
|| advise->mode == ADVISE_DISCARD
|| advise->mode == ADVISE_DISCARD_2
|| advise->mode == ADVISE_DISCARD_8
|| advise->mode == ADVISE_DISCARD_32
) {
int ret;
/* advise sequential access */
ret = posix_fadvise(f, 0, 0, POSIX_FADV_SEQUENTIAL);
if (ret != 0) {
/* LCOV_EXCL_START */
errno = ret; /* posix_fadvise return the error code */
return -1;
/* LCOV_EXCL_STOP */
}
}
#endif
return 0;
}
int advise_write(struct advise_struct* advise, int f, size_t offset, size_t size)
{
(void)advise;
(void)f;
(void)offset;
(void)size;
#if HAVE_SYNC_FILE_RANGE
if (advise->mode == ADVISE_FLUSH) {
int ret;
/* start writing immediately */
ret = sync_file_range(f, offset, size, SYNC_FILE_RANGE_WRITE);
if (ret != 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
}
#endif
/*
* Follow Linus recommendations about fast writes.
*
* Linus "Unexpected splice "always copy" behavior observed"
* http://thread.gmane.org/gmane.linux.kernel/987247/focus=988070
* ---
* I have had _very_ good experiences with even a rather trivial
* file writer that basically used (iirc) 8MB windows, and the logic was very
* trivial:
*
* - before writing a new 8M window, do "start writeback"
* (SYNC_FILE_RANGE_WRITE) on the previous window, and do
* a wait (SYNC_FILE_RANGE_WAIT_AFTER) on the window before that.
*
* in fact, in its simplest form, you can do it like this (this is from my
* "overwrite disk images" program that I use on old disks):
*
* for (index = 0; index < max_index ;index++) {
* if (write(fd, buffer, BUFSIZE) != BUFSIZE)
* break;
* // This won't block, but will start writeout asynchronously
* sync_file_range(fd, index*BUFSIZE, BUFSIZE, SYNC_FILE_RANGE_WRITE);
* // This does a blocking write-and-wait on any old ranges
* if (index)
* sync_file_range(fd, (index-1)*BUFSIZE, BUFSIZE, SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE|SYNC_FILE_RANGE_WAIT_AFTER);
* }
*
* and even if you don't actually do a discard (maybe we should add a
* SYNC_FILE_RANGE_DISCARD bit, right now you'd need to do a separate
* fadvise(FADV_DONTNEED) to throw it out) the system behavior is pretty
* nice, because the heavy writer gets good IO performance _and_ leaves only
* easy-to-free pages around after itself.
* ---
*
* Linus "Unexpected splice "always copy" behavior observed"
* http://thread.gmane.org/gmane.linux.kernel/987247/focus=988176
* ---
* The behavior for dirty page writeback is _not_ welldefined, and
* if you do POSIX_FADV_DONTNEED, I would suggest you do it as part of that
* writeback logic, ie you do it only on ranges that you have just waited on.
*
* IOW, in my example, you'd couple the
*
* sync_file_range(fd, (index-1)*BUFSIZE, BUFSIZE, SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE|SYNC_FILE_RANGE_WAIT_AFTER);
*
* with a
*
* posix_fadvise(fd, (index-1)*BUFSIZE, BUFSIZE, POSIX_FADV_DONTNEED);
*
* afterwards to throw out the pages that you just waited for.
* ---
*/
#if HAVE_SYNC_FILE_RANGE && HAVE_POSIX_FADVISE
if (advise->mode == ADVISE_DISCARD
|| advise->mode == ADVISE_DISCARD_2
|| advise->mode == ADVISE_DISCARD_8
|| advise->mode == ADVISE_DISCARD_32
) {
size_t dirty_offset = advise->dirty_begin;
size_t dirty_size = advise->dirty_end - advise->dirty_begin;
size_t limit;
switch (advise->mode) {
default:
case ADVISE_DISCARD : limit = 1; break;
case ADVISE_DISCARD_2 : limit = 2 * 1024 * 1024; break;
case ADVISE_DISCARD_8 : limit = 8 * 1024 * 1024; break;
case ADVISE_DISCARD_32 : limit = 32 * 1024 * 1024; break;
}
/* if we have to discard */
if (dirty_size >= limit) {
int ret;
/* send the data to the disk and wait until it's written */
ret = sync_file_range(f, dirty_offset, dirty_size, SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER);
if (ret != 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
/* flush the data from the cache */
ret = posix_fadvise(f, dirty_offset, dirty_size, POSIX_FADV_DONTNEED);
if (ret != 0) {
/* LCOV_EXCL_START */
errno = ret; /* posix_fadvise return the error code */
return -1;
/* LCOV_EXCL_STOP */
}
/* set the new dirty range */
advise->dirty_begin = offset;
advise->dirty_end = offset + size;
} else {
/* extend the dirty range */
if (advise->dirty_begin > offset)
advise->dirty_begin = offset;
if (advise->dirty_end < offset + size)
advise->dirty_end = offset + size;
}
}
#endif
return 0;
}
int advise_read(struct advise_struct* advise, int f, size_t offset, size_t size)
{
(void)advise;
(void)f;
(void)offset;
(void)size;
#if HAVE_POSIX_FADVISE
if (advise->mode == ADVISE_DISCARD
|| advise->mode == ADVISE_DISCARD_2
|| advise->mode == ADVISE_DISCARD_8
|| advise->mode == ADVISE_DISCARD_32
) {
int ret;
/* flush the data from the cache */
ret = posix_fadvise(f, offset, size, POSIX_FADV_DONTNEED);
if (ret != 0) {
/* LCOV_EXCL_START */
errno = ret; /* posix_fadvise return the error code */
return -1;
/* LCOV_EXCL_STOP */
}
}
#endif
/*
* Here we cannot call posix_fadvise(..., POSIX_FADV_WILLNEED) for the next block
* because it may be blocking.
*
* Ted Ts'o "posix_fadvise(POSIX_FADV_WILLNEED) waits before returning?"
* https://lkml.org/lkml/2010/12/6/122
* ---
* readahead and posix_fadvise(POSIX_FADV_WILLNEED) work exactly the same
* way, and in fact share mostly the same code path (see
* force_page_cache_readahead() in mm/readahead.c).
*
* They are asynchronous in that there is no guarantee the pages will be
* in the page cache by the time they return. But at the same time, they
* are not guaranteed to be non-blocking. That is, the work of doing the
* readahead does not take place in a kernel thread. So if you try to
* request I/O than will fit in the request queue, the system call will
* block until some I/O is completed so that more I/O requested cam be
* loaded onto the request queue.
*
* The only way to fix this would be to either put the work on a kernel
* thread (i.e., some kind of workqueue) or in a userspace thread. For
* ion programmer wondering what to do today, I'd suggest the
* latter since it will be more portable across various kernel versions.
*
* This does leave the question about whether we should change the kernel
* to allow readahead() and posix_fadvise(POSIX_FADV_WILLNEED) to be
* non-blocking and do this work in a workqueue (or via some kind of
* callback/continuation scheme). My worry is just doing this if a user
* application does something crazy, like request gigabytes and gigabytes
* of readahead, and then repents of their craziness, there should be a
* way of cancelling the readahead request. Today, the user can just
* kill the application. But if we simply shove the work to a kernel
* thread, it becomes a lot harder to cancel the readahead request. We'd
* have to invent a new API, and then have a way to know whether the user
* has access to kill a particular readahead request, etc.
* ---
*/
return 0;
}
/****************************************************************************/
/* memory */

View file

@ -322,6 +322,34 @@ int mkancestor(const char* file);
*/
int fmtime(int f, int64_t mtime_sec, int mtime_nsec);
/****************************************************************************/
/* advise */
/**
* Advise modes.
*/
#define ADVISE_DEFAULT 0 /**< Default mode. */
#define ADVISE_NONE 1 /**< Bare read/write mode. */
#define ADVISE_SEQUENTIAL 2 /**< Sequential mode. */
#define ADVISE_FLUSH 3 /**< Flush mode. */
#define ADVISE_DISCARD 4 /**< Discard the cache after every operation. */
#define ADVISE_DISCARD_2 5 /**< Discard the cache every 2MB. */
#define ADVISE_DISCARD_8 6 /**< Discard the cache every 8MB. */
#define ADVISE_DISCARD_32 7 /**< Discard the cache every 32MB. */
#define ADVISE_DIRECT 8 /**< Direct mode. */
struct advise_struct {
int mode;
size_t dirty_begin;
size_t dirty_end;
};
void advise_init(struct advise_struct* advise, int mode);
int advise_flags(struct advise_struct* advise);
int advise_open(struct advise_struct* advise, int f);
int advise_write(struct advise_struct* advise, int f, size_t offset, size_t size);
int advise_read(struct advise_struct* advise, int f, size_t offset, size_t size);
/****************************************************************************/
/* memory */

View file

@ -35,6 +35,7 @@ static int state_hash_process(struct snapraid_state* state, block_off_t blocksta
block_off_t i;
unsigned j;
void* buffer;
void* buffer_alloc;
data_off_t countsize;
block_off_t countpos;
block_off_t countmax;
@ -49,7 +50,7 @@ static int state_hash_process(struct snapraid_state* state, block_off_t blocksta
handle = handle_mapping(state, &diskmax);
/* buffer for reading */
buffer = malloc_nofail(state->block_size);
buffer = malloc_nofail_direct(state->block_size, &buffer_alloc);
if (!state->opt.skip_self)
mtest_vector(1, state->block_size, &buffer);
@ -383,7 +384,7 @@ bail:
finish:
free(handle);
free(buffer);
free(buffer_alloc);
if (error + io_error + silent_error != 0)
return -1;

View file

@ -1419,5 +1419,21 @@ void os_clear(void)
printf("\033[2J"); /* clear screen */
}
size_t direct_size(void)
{
long size;
size = sysconf(_SC_PAGESIZE);
if (size == -1) {
/* LCOV_EXCL_START */
log_fatal("No page size\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
return size;
}
#endif

View file

@ -20,6 +20,7 @@
#ifdef __linux__
#define HAVE_LINUX_DEVICE 1 /**< In Linux enables special device support. */
#define HAVE_DIRECT_IO 1 /**< Support O_DIRECT in open(). */
#endif
#define O_BINARY 0 /**< Not used in Unix. */
@ -57,5 +58,10 @@ int dirent_hidden(struct dirent* dd);
*/
const char* stat_desc(struct stat* st);
/**
* Return the aligment requirement for direct IO.
*/
size_t direct_size(void);
#endif

View file

@ -29,7 +29,23 @@ void* malloc_nofail_align(size_t size, void** freeptr)
{
void* ptr;
ptr = raid_malloc_align(size, freeptr);
ptr = raid_malloc(size, freeptr);
if (!ptr) {
/* LCOV_EXCL_START */
malloc_fail(size);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
return ptr;
}
void* malloc_nofail_direct(size_t size, void** freeptr)
{
void* ptr;
ptr = raid_malloc_align(size, direct_size(), freeptr);
if (!ptr) {
/* LCOV_EXCL_START */
@ -57,6 +73,22 @@ void** malloc_nofail_vector_align(int nd, int n, size_t size, void** freeptr)
return ptr;
}
void** malloc_nofail_vector_direct(int nd, int n, size_t size, void** freeptr)
{
void* ptr;
ptr = raid_malloc_vector_align(nd, n, size, direct_size(), 0, freeptr);
if (!ptr) {
/* LCOV_EXCL_START */
malloc_fail(n * size);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
return ptr;
}
void* malloc_nofail_test(size_t size)
{
void* ptr;

View file

@ -27,12 +27,23 @@
*/
void* malloc_nofail_align(size_t size, void** freeptr);
/**
* Safe aligned malloc. Usable for direct io.
*/
void* malloc_nofail_direct(size_t size, void** freeptr);
/**
* Safe aligned vector allocation.
* If no memory is available, it aborts.
*/
void** malloc_nofail_vector_align(int nd, int n, size_t size, void** freeptr);
/**
* Safe page vector allocation. Usable for direct io.
* If no memory is available, it aborts.
*/
void** malloc_nofail_vector_direct(int nd, int n, size_t size, void** freeptr);
/**
* Safe allocation with memory test.
*/

View file

@ -95,7 +95,8 @@ AC_CHECK_MEMBERS([struct statfs.f_fstypename], [], [], [[
dnl Checks for library functions.
AC_CHECK_FUNCS([memset strchr strerror strrchr mkdir gettimeofday strtoul])
AC_CHECK_FUNCS([getopt getopt_long snprintf vsnprintf sigaction])
AC_CHECK_FUNCS([ftruncate fallocate fsync access posix_fallocate posix_fadvise])
AC_CHECK_FUNCS([ftruncate fallocate access])
AC_CHECK_FUNCS([fsync posix_fadvise sync_file_range])
AC_CHECK_FUNCS([getc_unlocked ferror_unlocked fnmatch])
AC_CHECK_FUNCS([futimes futimens futimesat localtime_r])
AC_CHECK_FUNCS([fstatat flock statfs])

View file

@ -15,12 +15,12 @@
#include "internal.h"
#include "memory.h"
void *raid_malloc_align(size_t size, void **freeptr)
void *raid_malloc_align(size_t size, size_t align_size, void **freeptr)
{
unsigned char *ptr;
uintptr_t offset;
ptr = malloc(size + RAID_MALLOC_ALIGN);
ptr = malloc(size + align_size);
if (!ptr) {
/* LCOV_EXCL_START */
return 0;
@ -29,15 +29,20 @@ void *raid_malloc_align(size_t size, void **freeptr)
*freeptr = ptr;
offset = ((uintptr_t)ptr) % RAID_MALLOC_ALIGN;
offset = ((uintptr_t)ptr) % align_size;
if (offset != 0)
ptr += RAID_MALLOC_ALIGN - offset;
ptr += align_size - offset;
return ptr;
}
void **raid_malloc_vector(int nd, int n, size_t size, void **freeptr)
void *raid_malloc(size_t size, void **freeptr)
{
return raid_malloc_align(size, RAID_MALLOC_ALIGN, freeptr);
}
void **raid_malloc_vector_align(int nd, int n, size_t size, size_t align_size, size_t displacement_size, void **freeptr)
{
void **v;
unsigned char *va;
@ -52,7 +57,7 @@ void **raid_malloc_vector(int nd, int n, size_t size, void **freeptr)
/* LCOV_EXCL_STOP */
}
va = raid_malloc_align(n * (size + RAID_MALLOC_DISPLACEMENT), freeptr);
va = raid_malloc_align(n * (size + displacement_size), align_size, freeptr);
if (!va) {
/* LCOV_EXCL_START */
free(v);
@ -62,7 +67,7 @@ void **raid_malloc_vector(int nd, int n, size_t size, void **freeptr)
for (i = 0; i < n; ++i) {
v[i] = va;
va += size + RAID_MALLOC_DISPLACEMENT;
va += size + displacement_size;
}
/* reverse order of the data blocks */
@ -77,6 +82,11 @@ void **raid_malloc_vector(int nd, int n, size_t size, void **freeptr)
return v;
}
void **raid_malloc_vector(int nd, int n, size_t size, void **freeptr)
{
return raid_malloc_vector_align(nd, n, size, RAID_MALLOC_ALIGN, RAID_MALLOC_DISPLACEMENT, freeptr);
}
void raid_mrand_vector(unsigned seed, int n, size_t size, void **vv)
{
unsigned char **v = (unsigned char **)vv;

View file

@ -16,7 +16,7 @@
#define __RAID_MEMORY_H
/**
* Memory alignment provided by raid_malloc_align().
* Memory alignment provided by raid_malloc().
*
* It should guarantee good cache performance everywhere.
*/
@ -58,17 +58,29 @@
/**
* Aligned malloc.
* Use an alignment suitable for the raid functions.
*/
void *raid_malloc_align(size_t size, void **freeptr);
void *raid_malloc(size_t size, void **freeptr);
/**
* Arbitrary aligned malloc.
*/
void *raid_malloc_align(size_t size, size_t align_size, void **freeptr);
/**
* Aligned vector allocation.
* Use an alignment suitable for the raid functions.
* Returns a vector of @n pointers, each one pointing to a block of
* the specified @size.
* The first @nd elements are reversed in order.
*/
void **raid_malloc_vector(int nd, int n, size_t size, void **freeptr);
/**
* Arbitrary aligned vector allocation.
*/
void **raid_malloc_vector_align(int nd, int n, size_t size, size_t align_size, size_t displacement_size, void **freeptr);
/**
* Fills the memory vector with pseudo-random data based on the specified seed.
*/