From 642e1c6994c52484b90facfeab534757d010feda Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Sat, 24 Sep 2022 07:10:51 +0900 Subject: [PATCH] fadvise: new command wrapping posix_fadvise For measuring and monitoring the performance aspect of a command, people may want to drop the page caches related to specified files. In some cases, writing 1 to /proc/sys/vm/drop_caches is overkill. The fadvise command can be used for dropping page caches related to specified files; the impact on a system is much smaller than /proc/sys/vm/drop_caches. Signed-off-by: Masatake YAMATO --- configure.ac | 4 + misc-utils/Makemodule.am | 9 ++ misc-utils/fadvise.1.adoc | 57 +++++++++++++ misc-utils/fadvise.c | 161 ++++++++++++++++++++++++++++++++++++ misc-utils/meson.build | 4 + tests/commands.sh | 1 + tests/expected/fadvise/drop | 23 ++++++ tests/ts/fadvise/drop | 57 +++++++++++++ 8 files changed, 316 insertions(+) create mode 100644 misc-utils/fadvise.1.adoc create mode 100644 misc-utils/fadvise.c create mode 100644 tests/expected/fadvise/drop create mode 100644 tests/ts/fadvise/drop diff --git a/configure.ac b/configure.ac index 3920ac6596..e760889ccf 100644 --- a/configure.ac +++ b/configure.ac @@ -1794,6 +1794,10 @@ UL_BUILD_INIT([pipesz]) UL_REQUIRES_LINUX([pipesz]) AM_CONDITIONAL([BUILD_PIPESZ], [test "x$build_pipesz" = xyes]) +UL_BUILD_INIT([fadvise], [check]) +UL_REQUIRES_LINUX([fadvise]) +AM_CONDITIONAL([BUILD_FADVISE], [test "x$build_fadvise" = xyes]) + UL_BUILD_INIT([getopt], [yes]) AM_CONDITIONAL([BUILD_GETOPT], [test "x$build_getopt" = xyes]) diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am index 2c9f7ead5c..74bc2d1db0 100644 --- a/misc-utils/Makemodule.am +++ b/misc-utils/Makemodule.am @@ -279,3 +279,12 @@ pipesz_SOURCES = misc-utils/pipesz.c pipesz_LDADD = $(LDADD) libcommon.la pipesz_CFLAGS = $(AM_CFLAGS) endif + +if BUILD_FADVISE +usrbin_exec_PROGRAMS += fadvise +MANPAGES += misc-utils/fadvise.1 +dist_noinst_DATA += misc-utils/fadvise.1.adoc +fadvise_SOURCES = misc-utils/fadvise.c +fadvise_LDADD = $(LDADD) libcommon.la +fadvise_CFLAGS = $(AM_CFLAGS) +endif diff --git a/misc-utils/fadvise.1.adoc b/misc-utils/fadvise.1.adoc new file mode 100644 index 0000000000..b4296e5c98 --- /dev/null +++ b/misc-utils/fadvise.1.adoc @@ -0,0 +1,57 @@ +//po4a: entry man manual += fadvise(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: fadvise + +== NAME + +fadvise - utility to use the posix_fadvise system call + +== SYNOPSIS + +*fadvise* [*-a* _advice_] [*-o* _offset_] [*-l* _length_] _filename_ + +*fadvise* [*-a* _advice_] [*-o* _offset_] [*-l* _length_] -d _file-descriptor_ + +== DESCRIPTION + +*fadvise* is a simple command wrapping posix_fadvise system call +that is for predeclaring an access pattern for file data. + +== OPTIONS + +*-d*, *--fd* _file-descriptor_:: +Apply the advice to the file specified with the file descriptor instead +of open a file specified with a file name. + +*-a*, *--advice* _advice_:: +See the command output with *--help* option for available values for +advice. If this option is omitted, "dontneed" is used as default advice. + +*-o*, *--offset* _offset_:: +Specifies the beginning offset of the range, in bytes. +If this option is omitted, 0 is used as default advice. + +*-l*, *--length* _length_:: +Specifies the length of the range, in bytes. +If this option is omitted, 0 is used as default advice. + +== AUTHORS + +mailto:yamato@redhat.com[Masatake YAMATO] + +== SEE ALSO + +*posix_fadvise*(2) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] + diff --git a/misc-utils/fadvise.c b/misc-utils/fadvise.c new file mode 100644 index 0000000000..3d8ffc9c6c --- /dev/null +++ b/misc-utils/fadvise.c @@ -0,0 +1,161 @@ +/* + * fadvise - utility to use the posix_fadvise(2) + * + * Copyright (C) 2022 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include + +#include "c.h" +#include "nls.h" +#include "strutils.h" + +static const struct advice { + const char *name; + int num; +} advices [] = { + { "normal", POSIX_FADV_NORMAL, }, + { "sequential", POSIX_FADV_SEQUENTIAL, }, + { "random", POSIX_FADV_RANDOM, }, + { "noreuse", POSIX_FADV_NOREUSE, }, + { "willneeded", POSIX_FADV_WILLNEED, }, + { "dontneed", POSIX_FADV_DONTNEED, }, +}; + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + size_t i; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] file\n"), program_invocation_short_name); + fprintf(out, _(" %s [options] --fd|-d file-descriptor\n"), program_invocation_short_name); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -a, --advice applying advice to the file (default: \"dontneed\")\n"), out); + fputs(_(" -l, --length length for range operations, in bytes\n"), out); + fputs(_(" -o, --offset offset for range operations, in bytes\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(23)); + + fputs(_("\nAvailable values for advice:\n"), out); + for (i = 0; i < ARRAY_SIZE(advices); i++) { + fprintf(out, " %s\n", + advices[i].name); + } + + printf(USAGE_MAN_TAIL("fadvise(1)")); + + exit(EXIT_SUCCESS); +} + +struct fadvise_args { + int fd; + off_t offset; + off_t len; + int advice; +}; + + +int main(int argc, char ** argv) +{ + int c; + int rc; + bool do_close = false; + + int fd = -1; + off_t offset = 0; + off_t len = 0; + int advice = POSIX_FADV_DONTNEED; + + static const struct option longopts[] = { + { "advice", required_argument, NULL, 'a' }, + { "fd", required_argument, NULL, 'd' }, + { "length", required_argument, NULL, 'l' }, + { "offset", required_argument, NULL, 'o' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + while ((c = getopt_long (argc, argv, "a:d:hl:o:", longopts, NULL)) != -1) { + switch (c) { + case 'a': + break; + case 'd': + fd = strtos32_or_err(optarg, + _("invalid fd argument")); + break; + case 'l': + len = strtosize_or_err(optarg, + _("invalid length argument")); + break; + case 'o': + offset = strtosize_or_err(optarg, + _("invalid offset argument")); + break; + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (optind == argc && fd == -1) { + warnx(_("no file specified")); + errtryhelp(EXIT_FAILURE); + } + + if (argc - optind > 0 && fd != -1) { + warnx(_("specify either file descriptor or file name")); + errtryhelp(EXIT_FAILURE); + } + + if (argc - optind > 1) { + warnx(_("specify one file descriptor or file name")); + errtryhelp(EXIT_FAILURE); + } + + if (fd == -1) { + fd = open(argv[optind], O_RDONLY); + if (fd < 0) + err(EXIT_FAILURE, _("cannot open %s"), argv[optind]); + do_close = true; + } + + rc = posix_fadvise(fd, + offset, len, + advice); + if (rc != 0) + warn(_("failed to advise")); + + if (do_close) + close(fd); + + return rc; +} diff --git a/misc-utils/meson.build b/misc-utils/meson.build index b0dcb801d5..d7dfe7372c 100644 --- a/misc-utils/meson.build +++ b/misc-utils/meson.build @@ -147,3 +147,7 @@ cal_sources = files( pipesz_sources = files( 'pipesz.c', ) + +fadvise_sources = files( + 'fadvise.c', +) diff --git a/tests/commands.sh b/tests/commands.sh index aff324c1f3..58223fe95f 100644 --- a/tests/commands.sh +++ b/tests/commands.sh @@ -61,6 +61,7 @@ TS_CMD_FALLOCATE=${TS_CMD_FALLOCATE-"${ts_commandsdir}fallocate"} TS_CMD_FDISK=${TS_CMD_FDISK-"${ts_commandsdir}fdisk"} TS_CMD_FLOCK=${TS_CMD_FLOCK-"${ts_commandsdir}flock"} TS_CMD_SFDISK=${TS_CMD_SFDISK-"${ts_commandsdir}sfdisk"} +TS_CMD_FADVISE=${TS_CMD_FADVISE-"${ts_commandsdir}fadvise"} TS_CMD_FINCORE=${TS_CMD_FINCORE-"${ts_commandsdir}fincore"} TS_CMD_FINDMNT=${TS_CMD_FINDMNT-"${ts_commandsdir}findmnt"} TS_CMD_FSCKCRAMFS=${TS_CMD_FSCKCRAMFS:-"${ts_commandsdir}fsck.cramfs"} diff --git a/tests/expected/fadvise/drop b/tests/expected/fadvise/drop new file mode 100644 index 0000000000..f2360b56fb --- /dev/null +++ b/tests/expected/fadvise/drop @@ -0,0 +1,23 @@ + RES PAGES SIZE FILE + 32K 8 32K ddtest + +whole file +status: 0 +RES PAGES SIZE FILE + 0B 0 32K ddtest + +offset: 8192 +status: 0 +RES PAGES SIZE FILE + 8K 2 32K ddtest + +length: 16384 +status: 0 + RES PAGES SIZE FILE + 16K 4 32K ddtest + +offset: 8192, length: 16384 fd: 42 +status: 0 + RES PAGES SIZE FILE + 16K 4 32K ddtest + diff --git a/tests/ts/fadvise/drop b/tests/ts/fadvise/drop new file mode 100644 index 0000000000..e8c0071915 --- /dev/null +++ b/tests/ts/fadvise/drop @@ -0,0 +1,57 @@ +#!/bin/bash + +TS_TOPDIR="${0%/*}/../.." +TS_DESC="drop page caches related to a file" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" + +ts_check_test_command "$TS_CMD_FADVISE" +ts_check_test_command "$TS_CMD_FINCORE" + +ts_check_prog "dd" +ts_check_prog "sleep" + +ts_cd "$TS_OUTDIR" + +FILE="ddtest" +BS=4k +COUNT=8 + +{ + dd if=/dev/zero of="$FILE" bs=$BS count=$COUNT >& /dev/null + "$TS_CMD_FINCORE" "$FILE" + echo + + dd if=/dev/zero of="$FILE" bs=$BS count=$COUNT >& /dev/null + echo "whole file" + "$TS_CMD_FADVISE" "$FILE" + echo status: $? + "$TS_CMD_FINCORE" "$FILE" + echo + + dd if=/dev/zero of="$FILE" bs=$BS count=$COUNT >& /dev/null + echo "offset: 8192" + "$TS_CMD_FADVISE" -o 8192 "$FILE" + echo status: $? + "$TS_CMD_FINCORE" "$FILE" + echo + + dd if=/dev/zero of="$FILE" bs=$BS count=$COUNT >& /dev/null + echo "length: 16384" + "$TS_CMD_FADVISE" -l 16384 "$FILE" + echo status: $? + "$TS_CMD_FINCORE" "$FILE" + echo + + dd if=/dev/zero of="$FILE" bs=$BS count=$COUNT >& /dev/null + echo "offset: 8192, length: 16384 fd: 42" + "$TS_CMD_FADVISE" -o 8192 -l 16384 --fd 42 42<"$FILE" + echo status: $? + "$TS_CMD_FINCORE" "$FILE" + echo + + rm "$FILE" +} >> "$TS_OUTPUT" 2>> "$TS_ERRLOG" + +ts_finalize -- 2.47.3