]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - misc-utils/fincore.c
include/pidfd-utils: remove hardcoded syscall fallback
[thirdparty/util-linux.git] / misc-utils / fincore.c
index 84b83c6f22c5506e67764a6c3b0d7be6cabbfebb..513b8111410e09c4d01a852acdb5b5536ff670bb 100644 (file)
@@ -31,6 +31,7 @@
 #include "closestream.h"
 #include "xalloc.h"
 #include "strutils.h"
+#include "blkdev.h"
 
 #include "libsmartcols.h"
 
    e.g. 128MB on x86_64. ( = N_PAGES_IN_WINDOW * 4096 ). */
 #define N_PAGES_IN_WINDOW ((size_t)(32 * 1024))
 
+#ifndef HAVE_CACHESTAT
+
+#ifndef SYS_cachestat
+#define SYS_cachestat 451
+#endif
+
+struct cachestat_range {
+       uint64_t off;
+       uint64_t len;
+};
+
+struct cachestat {
+       uint64_t nr_cache;
+       uint64_t nr_dirty;
+       uint64_t nr_writeback;
+       uint64_t nr_evicted;
+       uint64_t nr_recently_evicted;
+};
+
+static inline int cachestat(unsigned int fd,
+                           const struct cachestat_range *cstat_range,
+                           struct cachestat *cstat, unsigned int flags)
+{
+       return syscall(SYS_cachestat, fd, cstat_range, cstat, flags);
+}
+
+#endif // HAVE_CACHESTAT
 
 struct colinfo {
-       const char *name;
+       const char * const name;
        double whint;
        int flags;
        const char *help;
+       unsigned int pages : 1;
 };
 
 enum {
        COL_PAGES,
        COL_SIZE,
        COL_FILE,
-       COL_RES
+       COL_RES,
+       COL_DIRTY_PAGES,
+       COL_DIRTY,
+       COL_WRITEBACK_PAGES,
+       COL_WRITEBACK,
+       COL_EVICTED_PAGES,
+       COL_EVICTED,
+       COL_RECENTLY_EVICTED_PAGES,
+       COL_RECENTLY_EVICTED,
 };
 
-static struct colinfo infos[] = {
-       [COL_PAGES]  = { "PAGES",    1, SCOLS_FL_RIGHT, N_("file data resident in memory in pages")},
-       [COL_RES]    = { "RES",      5, SCOLS_FL_RIGHT, N_("file data resident in memory in bytes")}, 
-       [COL_SIZE]   = { "SIZE",     5, SCOLS_FL_RIGHT, N_("size of the file")},
-       [COL_FILE]   = { "FILE",     4, 0, N_("file name")},
+static const struct colinfo infos[] = {
+       [COL_PAGES]                  = { "PAGES",                  1, SCOLS_FL_RIGHT, N_("file data resident in memory in pages"), 1},
+       [COL_RES]                    = { "RES",                    5, SCOLS_FL_RIGHT, N_("file data resident in memory in bytes")},
+       [COL_SIZE]                   = { "SIZE",                   5, SCOLS_FL_RIGHT, N_("size of the file")},
+       [COL_FILE]                   = { "FILE",                   4, 0, N_("file name")},
+       [COL_DIRTY_PAGES]            = { "DIRTY_PAGES",            1, SCOLS_FL_RIGHT, N_("number of dirty pages"), 1},
+       [COL_DIRTY]                  = { "DIRTY",                  5, SCOLS_FL_RIGHT, N_("number of dirty bytes")},
+       [COL_WRITEBACK_PAGES]        = { "WRITEBACK_PAGES",        1, SCOLS_FL_RIGHT, N_("number of pages marked for writeback"), 1},
+       [COL_WRITEBACK]              = { "WRITEBACK",              5, SCOLS_FL_RIGHT, N_("number of bytes marked for writeback")},
+       [COL_EVICTED_PAGES]          = { "EVICTED_PAGES",          1, SCOLS_FL_RIGHT, N_("number of evicted pages"), 1},
+       [COL_EVICTED]                = { "EVICTED",                5, SCOLS_FL_RIGHT, N_("number of evicted bytes")},
+       [COL_RECENTLY_EVICTED_PAGES] = { "RECENTLY_EVICTED_PAGES", 1, SCOLS_FL_RIGHT, N_("number of recently evicted pages"), 1},
+       [COL_RECENTLY_EVICTED]       = { "RECENTLY_EVICTED",       5, SCOLS_FL_RIGHT, N_("number of recently evicted bytes")},
 };
 
 static int columns[ARRAY_SIZE(infos) * 2] = {-1};
@@ -76,6 +121,20 @@ struct fincore_control {
                     noheadings : 1,
                     raw : 1,
                     json : 1;
+
+};
+
+struct fincore_state {
+       const char * const name;
+       long long unsigned int file_size;
+
+       struct cachestat cstat;
+       struct {
+               unsigned int dirty : 1,
+                            writeback : 1,
+                            evicted : 1,
+                            recently_evicted : 1;
+       } cstat_fields;
 };
 
 
@@ -106,13 +165,49 @@ static const struct colinfo *get_column_info(int num)
        return &infos[ get_column_id(num) ];
 }
 
+static int get_cstat_value(const struct fincore_state *st, int column_id,
+                          uint64_t *value)
+{
+       switch(column_id) {
+       case COL_PAGES:
+       case COL_RES:
+               *value = st->cstat.nr_cache;
+               return 1;
+       case COL_DIRTY_PAGES:
+       case COL_DIRTY:
+               if (!st->cstat_fields.dirty)
+                       break;
+               *value = st->cstat.nr_dirty;
+               return 1;
+       case COL_WRITEBACK_PAGES:
+       case COL_WRITEBACK:
+               if (!st->cstat_fields.writeback)
+               *value = st->cstat.nr_writeback;
+               return 1;
+       case COL_EVICTED_PAGES:
+       case COL_EVICTED:
+               if (!st->cstat_fields.evicted)
+                       break;
+               *value = st->cstat.nr_evicted;
+               return 1;
+       case COL_RECENTLY_EVICTED_PAGES:
+       case COL_RECENTLY_EVICTED:
+               if (!st->cstat_fields.recently_evicted)
+                       break;
+               *value = st->cstat.nr_recently_evicted;
+               return 1;
+       default:
+               assert(0);
+       }
+       return 0;
+}
+
 static int add_output_data(struct fincore_control *ctl,
-                          const char *name,
-                          off_t file_size,
-                          off_t count_incore)
+                          struct fincore_state *st)
 {
        size_t i;
        char *tmp;
+       uint64_t value = 0;
        struct libscols_line *ln;
 
        assert(ctl);
@@ -124,37 +219,49 @@ static int add_output_data(struct fincore_control *ctl,
 
        for (i = 0; i < ncolumns; i++) {
                int rc = 0;
+               int column_id = get_column_id(i);
+               int format_value = 0;
 
-               switch(get_column_id(i)) {
+               switch(column_id) {
                case COL_FILE:
-                       rc = scols_line_set_data(ln, i, name);
-                       break;
-               case COL_PAGES:
-                       xasprintf(&tmp, "%jd",  (intmax_t) count_incore);
-                       rc = scols_line_refer_data(ln, i, tmp);
+                       rc = scols_line_set_data(ln, i, st->name);
                        break;
-               case COL_RES:
-               {
-                       uintmax_t res = (uintmax_t) count_incore * ctl->pagesize;
-
-                       if (ctl->bytes)
-                               xasprintf(&tmp, "%ju", res);
-                       else
-                               tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, res);
-                       rc = scols_line_refer_data(ln, i, tmp);
-                       break;
-               }
                case COL_SIZE:
                        if (ctl->bytes)
-                               xasprintf(&tmp, "%jd", (intmax_t) file_size);
+                               xasprintf(&tmp, "%jd", (intmax_t) st->file_size);
                        else
-                               tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, file_size);
+                               tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, st->file_size);
                        rc = scols_line_refer_data(ln, i, tmp);
                        break;
+               case COL_PAGES:
+               case COL_RES:
+               case COL_DIRTY_PAGES:
+               case COL_DIRTY:
+               case COL_WRITEBACK_PAGES:
+               case COL_WRITEBACK:
+               case COL_EVICTED:
+               case COL_EVICTED_PAGES:
+               case COL_RECENTLY_EVICTED:
+               case COL_RECENTLY_EVICTED_PAGES:
+                       format_value = get_cstat_value(st, column_id, &value);
+                       break;
                default:
                        return -EINVAL;
                }
 
+               if (format_value) {
+                       if (get_column_info(i)->pages) {
+                               xasprintf(&tmp, "%ju", (uintmax_t) value);
+                       } else {
+                               value *= ctl->pagesize;
+                               if (ctl->bytes)
+                                       xasprintf(&tmp, "%ju", (uintmax_t) value);
+                               else
+                                       tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, value);
+                       }
+                       rc = scols_line_refer_data(ln, i, tmp);
+               }
+
                if (rc)
                        err(EXIT_FAILURE, _("failed to add output data"));
        }
@@ -164,14 +271,13 @@ static int add_output_data(struct fincore_control *ctl,
 
 static int do_mincore(struct fincore_control *ctl,
                      void *window, const size_t len,
-                     const char *name,
-                     off_t *count_incore)
+                     struct fincore_state *st)
 {
        static unsigned char vec[N_PAGES_IN_WINDOW];
        int n = (len / ctl->pagesize) + ((len % ctl->pagesize)? 1: 0);
 
        if (mincore (window, len, vec) < 0) {
-               warn(_("failed to do mincore: %s"), name);
+               warn(_("failed to do mincore: %s"), st->name);
                return -errno;
        }
 
@@ -180,43 +286,37 @@ static int do_mincore(struct fincore_control *ctl,
                if (vec[--n] & 0x1)
                {
                        vec[n] = 0;
-                       (*count_incore)++;
+                       st->cstat.nr_cache++;
                }
        }
 
        return 0;
 }
 
-static int fincore_fd (struct fincore_control *ctl,
+static int mincore_fd (struct fincore_control *ctl,
                       int fd,
-                      const char *name,
-                      off_t file_size,
-                      off_t *count_incore)
+                      struct fincore_state *st)
 {
        size_t window_size = N_PAGES_IN_WINDOW * ctl->pagesize;
-       off_t  file_offset;
-       void  *window = NULL;
+       long long unsigned int file_offset, len;
        int rc = 0;
-       int warned_once = 0;
 
-       for (file_offset = 0; file_offset < file_size; file_offset += window_size) {
-               size_t len;
+       for (file_offset = 0; file_offset < st->file_size; file_offset += len) {
+               void  *window = NULL;
 
-               len = file_size - file_offset;
+               len = st->file_size - file_offset;
                if (len >= window_size)
                        len = window_size;
 
-               window = mmap(window, len, PROT_NONE, MAP_PRIVATE, fd, file_offset);
+               /* PROT_NONE is enough for Linux, but qemu-user wants PROT_READ */
+               window = mmap(window, len, PROT_READ, MAP_PRIVATE, fd, file_offset);
                if (window == MAP_FAILED) {
-                       if (!warned_once) {
-                               rc = -EINVAL;
-                               warn(_("failed to do mmap: %s"), name);
-                               warned_once = 1;
-                       }
+                       rc = -EINVAL;
+                       warn(_("failed to do mmap: %s"), st->name);
                        break;
                }
 
-               rc = do_mincore(ctl, window, len, name, count_incore);
+               rc = do_mincore(ctl, window, len, st);
                if (rc)
                        break;
 
@@ -226,40 +326,71 @@ static int fincore_fd (struct fincore_control *ctl,
        return rc;
 }
 
+static int fincore_fd (struct fincore_control *ctl,
+                      int fd,
+                      struct fincore_state *st)
+{
+       int rc;
+       const struct cachestat_range cstat_range = { 0 };
+
+       rc = cachestat(fd, &cstat_range, &st->cstat, 0);
+       if (!rc) {
+               st->cstat_fields.dirty = 1;
+               st->cstat_fields.writeback = 1;
+               st->cstat_fields.evicted = 1;
+               st->cstat_fields.recently_evicted = 1;
+               return 0;
+       }
+
+       if (errno != ENOSYS)
+               warn(_("failed to do cachestat: %s"), st->name);
+
+       return mincore_fd(ctl, fd, st);
+}
+
 /*
  * Returns: <0 on error, 0 success, 1 ignore.
  */
 static int fincore_name(struct fincore_control *ctl,
-                       const char *name,
-                       struct stat *sb,
-                       off_t *count_incore)
+                       struct fincore_state *st)
 {
        int fd;
        int rc = 0;
+       struct stat sb;
 
-       if ((fd = open (name, O_RDONLY)) < 0) {
-               warn(_("failed to open: %s"), name);
+       if ((fd = open (st->name, O_RDONLY)) < 0) {
+               warn(_("failed to open: %s"), st->name);
                return -errno;
        }
 
-       if (fstat (fd, sb) < 0) {
-               warn(_("failed to do fstat: %s"), name);
+       if (fstat (fd, &sb) < 0) {
+               warn(_("failed to do fstat: %s"), st->name);
                close (fd);
                return -errno;
        }
+       st->file_size = sb.st_size;
 
-       if (S_ISDIR(sb->st_mode))
-               rc = 1;                 /* ignore */
+       if (S_ISBLK(sb.st_mode)) {
+               rc = blkdev_get_size(fd, &st->file_size);
+               if (rc)
+                       warn(_("failed ioctl to get size: %s"), st->name);
+       } else if (S_ISREG(sb.st_mode)) {
+               st->file_size = sb.st_size;
+       } else {
+               rc = 1;                 /* ignore things like symlinks
+                                        * and directories*/
+       }
 
-       else if (sb->st_size)
-               rc = fincore_fd(ctl, fd, name, sb->st_size, count_incore);
+       if (!rc)
+               rc = fincore_fd(ctl, fd, st);
 
        close (fd);
        return rc;
 }
 
-static void __attribute__((__noreturn__)) usage(FILE *out)
+static void __attribute__((__noreturn__)) usage(void)
 {
+       FILE *out = stdout;
        size_t i;
 
        fputs(USAGE_HEADER, out);
@@ -270,20 +401,20 @@ static void __attribute__((__noreturn__)) usage(FILE *out)
        fputs(_(" -b, --bytes           print sizes in bytes rather than in human readable format\n"), out);
        fputs(_(" -n, --noheadings      don't print headings\n"), out);
        fputs(_(" -o, --output <list>   output columns\n"), out);
+       fputs(_("     --output-all      output all columns\n"), out);
        fputs(_(" -r, --raw             use raw output format\n"), out);
 
        fputs(USAGE_SEPARATOR, out);
-       fputs(USAGE_HELP, out);
-       fputs(USAGE_VERSION, out);
+       fprintf(out, USAGE_HELP_OPTIONS(23));
 
-       fprintf(out, USAGE_COLUMNS);
+       fputs(USAGE_COLUMNS, out);
 
        for (i = 0; i < ARRAY_SIZE(infos); i++)
-               fprintf(out, " %11s  %s\n", infos[i].name, _(infos[i].help));
+               fprintf(out, " %22s  %s\n", infos[i].name, _(infos[i].help));
 
        fprintf(out, USAGE_MAN_TAIL("fincore(1)"));
 
-       exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+       exit(EXIT_SUCCESS);
 }
 
 int main(int argc, char ** argv)
@@ -297,10 +428,14 @@ int main(int argc, char ** argv)
                .pagesize = getpagesize()
        };
 
+       enum {
+               OPT_OUTPUT_ALL = CHAR_MAX + 1
+       };
        static const struct option longopts[] = {
                { "bytes",      no_argument, NULL, 'b' },
                { "noheadings", no_argument, NULL, 'n' },
                { "output",     required_argument, NULL, 'o' },
+               { "output-all", no_argument,       NULL, OPT_OUTPUT_ALL },
                { "version",    no_argument, NULL, 'V' },
                { "help",       no_argument, NULL, 'h' },
                { "json",       no_argument, NULL, 'J' },
@@ -311,7 +446,7 @@ int main(int argc, char ** argv)
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
-       atexit(close_stdout);
+       close_stdout_atexit();
 
        while ((c = getopt_long (argc, argv, "bno:JrVh", longopts, NULL)) != -1) {
                switch (c) {
@@ -324,6 +459,10 @@ int main(int argc, char ** argv)
                case 'o':
                        outarg = optarg;
                        break;
+               case OPT_OUTPUT_ALL:
+                       for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++)
+                               columns[ncolumns] = ncolumns;
+                       break;
                case 'J':
                        ctl.json = 1;
                        break;
@@ -331,10 +470,9 @@ int main(int argc, char ** argv)
                        ctl.raw = 1;
                        break;
                case 'V':
-                       printf(UTIL_LINUX_VERSION);
-                       return EXIT_SUCCESS;
+                       print_version(EXIT_SUCCESS);
                case 'h':
-                       usage(stdout);
+                       usage();
                default:
                        errtryhelp(EXIT_FAILURE);
                }
@@ -369,19 +507,39 @@ int main(int argc, char ** argv)
 
        for (i = 0; i < ncolumns; i++) {
                const struct colinfo *col = get_column_info(i);
+               struct libscols_column *cl;
 
-               if (!scols_table_new_column(ctl.tb, col->name, col->whint, col->flags))
+               cl = scols_table_new_column(ctl.tb, col->name, col->whint, col->flags);
+               if (!cl)
                        err(EXIT_FAILURE, _("failed to allocate output column"));
+
+               if (ctl.json) {
+                       int id = get_column_id(i);
+
+                       switch (id) {
+                       case COL_FILE:
+                               scols_column_set_json_type(cl, SCOLS_JSON_STRING);
+                               break;
+                       case COL_SIZE:
+                       case COL_RES:
+                               if (!ctl.bytes)
+                                       break;
+                               /* fallthrough */
+                       default:
+                               scols_column_set_json_type(cl, SCOLS_JSON_NUMBER);
+                               break;
+                       }
+               }
        }
 
        for(; optind < argc; optind++) {
-               char *name = argv[optind];
-               struct stat sb;
-               off_t count_incore = 0;
+               struct fincore_state st = {
+                       .name = argv[optind],
+               };
 
-               switch (fincore_name(&ctl, name, &sb, &count_incore)) {
+               switch (fincore_name(&ctl, &st)) {
                case 0:
-                       add_output_data(&ctl, name, sb.st_size, count_incore);
+                       add_output_data(&ctl, &st);
                        break;
                case 1:
                        break; /* ignore */