2 * fincore - count pages of file contents in core
4 * Copyright (C) 2017 Red Hat, Inc. All rights reserved.
5 * Written by Masatake YAMATO <yamato@redhat.com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it would be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
31 #include "closestream.h"
36 #include "libsmartcols.h"
38 /* For large files, mmap is called in iterative way.
39 Window is the unit of vma prepared in each mmap
42 Window size depends on page size.
43 e.g. 128MB on x86_64. ( = N_PAGES_IN_WINDOW * 4096 ). */
44 #define N_PAGES_IN_WINDOW ((size_t)(32 * 1024))
46 #ifndef HAVE_CACHESTAT
49 #define SYS_cachestat 451
52 struct cachestat_range
{
60 uint64_t nr_writeback
;
62 uint64_t nr_recently_evicted
;
65 static inline int cachestat(unsigned int fd
,
66 const struct cachestat_range
*cstat_range
,
67 struct cachestat
*cstat
, unsigned int flags
)
69 return syscall(SYS_cachestat
, fd
, cstat_range
, cstat
, flags
);
72 #endif // HAVE_CACHESTAT
75 const char * const name
;
79 unsigned int pages
: 1;
93 COL_RECENTLY_EVICTED_PAGES
,
97 static const struct colinfo infos
[] = {
98 [COL_PAGES
] = { "PAGES", 1, SCOLS_FL_RIGHT
, N_("file data resident in memory in pages"), 1},
99 [COL_RES
] = { "RES", 5, SCOLS_FL_RIGHT
, N_("file data resident in memory in bytes")},
100 [COL_SIZE
] = { "SIZE", 5, SCOLS_FL_RIGHT
, N_("size of the file")},
101 [COL_FILE
] = { "FILE", 4, 0, N_("file name")},
102 [COL_DIRTY_PAGES
] = { "DIRTY_PAGES", 1, SCOLS_FL_RIGHT
, N_("number of dirty pages"), 1},
103 [COL_DIRTY
] = { "DIRTY", 5, SCOLS_FL_RIGHT
, N_("number of dirty bytes")},
104 [COL_WRITEBACK_PAGES
] = { "WRITEBACK_PAGES", 1, SCOLS_FL_RIGHT
, N_("number of pages marked for writeback"), 1},
105 [COL_WRITEBACK
] = { "WRITEBACK", 5, SCOLS_FL_RIGHT
, N_("number of bytes marked for writeback")},
106 [COL_EVICTED_PAGES
] = { "EVICTED_PAGES", 1, SCOLS_FL_RIGHT
, N_("number of evicted pages"), 1},
107 [COL_EVICTED
] = { "EVICTED", 5, SCOLS_FL_RIGHT
, N_("number of evicted bytes")},
108 [COL_RECENTLY_EVICTED_PAGES
] = { "RECENTLY_EVICTED_PAGES", 1, SCOLS_FL_RIGHT
, N_("number of recently evicted pages"), 1},
109 [COL_RECENTLY_EVICTED
] = { "RECENTLY_EVICTED", 5, SCOLS_FL_RIGHT
, N_("number of recently evicted bytes")},
112 static int columns
[ARRAY_SIZE(infos
) * 2] = {-1};
113 static size_t ncolumns
;
115 struct fincore_control
{
116 const size_t pagesize
;
118 struct libscols_table
*tb
; /* output */
120 unsigned int bytes
: 1,
127 struct fincore_state
{
128 const char * const name
;
129 long long unsigned int file_size
;
131 struct cachestat cstat
;
133 unsigned int dirty
: 1,
136 recently_evicted
: 1;
141 static int column_name_to_id(const char *name
, size_t namesz
)
145 for (i
= 0; i
< ARRAY_SIZE(infos
); i
++) {
146 const char *cn
= infos
[i
].name
;
148 if (!strncasecmp(name
, cn
, namesz
) && !*(cn
+ namesz
))
151 warnx(_("unknown column: %s"), name
);
155 static int get_column_id(int num
)
158 assert((size_t) num
< ncolumns
);
159 assert(columns
[num
] < (int) ARRAY_SIZE(infos
));
163 static const struct colinfo
*get_column_info(int num
)
165 return &infos
[ get_column_id(num
) ];
168 static int get_cstat_value(const struct fincore_state
*st
, int column_id
,
174 *value
= st
->cstat
.nr_cache
;
176 case COL_DIRTY_PAGES
:
178 if (!st
->cstat_fields
.dirty
)
180 *value
= st
->cstat
.nr_dirty
;
182 case COL_WRITEBACK_PAGES
:
184 if (!st
->cstat_fields
.writeback
)
185 *value
= st
->cstat
.nr_writeback
;
187 case COL_EVICTED_PAGES
:
189 if (!st
->cstat_fields
.evicted
)
191 *value
= st
->cstat
.nr_evicted
;
193 case COL_RECENTLY_EVICTED_PAGES
:
194 case COL_RECENTLY_EVICTED
:
195 if (!st
->cstat_fields
.recently_evicted
)
197 *value
= st
->cstat
.nr_recently_evicted
;
205 static int add_output_data(struct fincore_control
*ctl
,
206 struct fincore_state
*st
)
211 struct libscols_line
*ln
;
216 ln
= scols_table_new_line(ctl
->tb
, NULL
);
218 err(EXIT_FAILURE
, _("failed to allocate output line"));
220 for (i
= 0; i
< ncolumns
; i
++) {
222 int column_id
= get_column_id(i
);
223 int format_value
= 0;
227 rc
= scols_line_set_data(ln
, i
, st
->name
);
231 xasprintf(&tmp
, "%jd", (intmax_t) st
->file_size
);
233 tmp
= size_to_human_string(SIZE_SUFFIX_1LETTER
, st
->file_size
);
234 rc
= scols_line_refer_data(ln
, i
, tmp
);
238 case COL_DIRTY_PAGES
:
240 case COL_WRITEBACK_PAGES
:
243 case COL_EVICTED_PAGES
:
244 case COL_RECENTLY_EVICTED
:
245 case COL_RECENTLY_EVICTED_PAGES
:
246 format_value
= get_cstat_value(st
, column_id
, &value
);
253 if (get_column_info(i
)->pages
) {
254 xasprintf(&tmp
, "%ju", (uintmax_t) value
);
256 value
*= ctl
->pagesize
;
258 xasprintf(&tmp
, "%ju", (uintmax_t) value
);
260 tmp
= size_to_human_string(SIZE_SUFFIX_1LETTER
, value
);
262 rc
= scols_line_refer_data(ln
, i
, tmp
);
266 err(EXIT_FAILURE
, _("failed to add output data"));
272 static int do_mincore(struct fincore_control
*ctl
,
273 void *window
, const size_t len
,
274 struct fincore_state
*st
)
276 static unsigned char vec
[N_PAGES_IN_WINDOW
];
277 int n
= (len
/ ctl
->pagesize
) + ((len
% ctl
->pagesize
)? 1: 0);
279 if (mincore (window
, len
, vec
) < 0) {
280 warn(_("failed to do mincore: %s"), st
->name
);
289 st
->cstat
.nr_cache
++;
296 static int mincore_fd (struct fincore_control
*ctl
,
298 struct fincore_state
*st
)
300 size_t window_size
= N_PAGES_IN_WINDOW
* ctl
->pagesize
;
301 long long unsigned int file_offset
, len
;
304 for (file_offset
= 0; file_offset
< st
->file_size
; file_offset
+= len
) {
307 len
= st
->file_size
- file_offset
;
308 if (len
>= window_size
)
311 /* PROT_NONE is enough for Linux, but qemu-user wants PROT_READ */
312 window
= mmap(window
, len
, PROT_READ
, MAP_PRIVATE
, fd
, file_offset
);
313 if (window
== MAP_FAILED
) {
315 warn(_("failed to do mmap: %s"), st
->name
);
319 rc
= do_mincore(ctl
, window
, len
, st
);
323 munmap (window
, len
);
329 static int fincore_fd (struct fincore_control
*ctl
,
331 struct fincore_state
*st
)
334 const struct cachestat_range cstat_range
= { 0 };
336 rc
= cachestat(fd
, &cstat_range
, &st
->cstat
, 0);
338 st
->cstat_fields
.dirty
= 1;
339 st
->cstat_fields
.writeback
= 1;
340 st
->cstat_fields
.evicted
= 1;
341 st
->cstat_fields
.recently_evicted
= 1;
346 warn(_("failed to do cachestat: %s"), st
->name
);
348 return mincore_fd(ctl
, fd
, st
);
352 * Returns: <0 on error, 0 success, 1 ignore.
354 static int fincore_name(struct fincore_control
*ctl
,
355 struct fincore_state
*st
)
361 if ((fd
= open (st
->name
, O_RDONLY
)) < 0) {
362 warn(_("failed to open: %s"), st
->name
);
366 if (fstat (fd
, &sb
) < 0) {
367 warn(_("failed to do fstat: %s"), st
->name
);
371 st
->file_size
= sb
.st_size
;
373 if (S_ISBLK(sb
.st_mode
)) {
374 rc
= blkdev_get_size(fd
, &st
->file_size
);
376 warn(_("failed ioctl to get size: %s"), st
->name
);
377 } else if (S_ISREG(sb
.st_mode
)) {
378 st
->file_size
= sb
.st_size
;
380 rc
= 1; /* ignore things like symlinks
385 rc
= fincore_fd(ctl
, fd
, st
);
391 static void __attribute__((__noreturn__
)) usage(void)
396 fputs(USAGE_HEADER
, out
);
397 fprintf(out
, _(" %s [options] file...\n"), program_invocation_short_name
);
399 fputs(USAGE_OPTIONS
, out
);
400 fputs(_(" -J, --json use JSON output format\n"), out
);
401 fputs(_(" -b, --bytes print sizes in bytes rather than in human readable format\n"), out
);
402 fputs(_(" -n, --noheadings don't print headings\n"), out
);
403 fputs(_(" -o, --output <list> output columns\n"), out
);
404 fputs(_(" --output-all output all columns\n"), out
);
405 fputs(_(" -r, --raw use raw output format\n"), out
);
407 fputs(USAGE_SEPARATOR
, out
);
408 printf(USAGE_HELP_OPTIONS(23));
410 fprintf(out
, USAGE_COLUMNS
);
412 for (i
= 0; i
< ARRAY_SIZE(infos
); i
++)
413 fprintf(out
, " %22s %s\n", infos
[i
].name
, _(infos
[i
].help
));
415 printf(USAGE_MAN_TAIL("fincore(1)"));
420 int main(int argc
, char ** argv
)
424 int rc
= EXIT_SUCCESS
;
427 struct fincore_control ctl
= {
428 .pagesize
= getpagesize()
432 OPT_OUTPUT_ALL
= CHAR_MAX
+ 1
434 static const struct option longopts
[] = {
435 { "bytes", no_argument
, NULL
, 'b' },
436 { "noheadings", no_argument
, NULL
, 'n' },
437 { "output", required_argument
, NULL
, 'o' },
438 { "output-all", no_argument
, NULL
, OPT_OUTPUT_ALL
},
439 { "version", no_argument
, NULL
, 'V' },
440 { "help", no_argument
, NULL
, 'h' },
441 { "json", no_argument
, NULL
, 'J' },
442 { "raw", no_argument
, NULL
, 'r' },
443 { NULL
, 0, NULL
, 0 },
446 setlocale(LC_ALL
, "");
447 bindtextdomain(PACKAGE
, LOCALEDIR
);
449 close_stdout_atexit();
451 while ((c
= getopt_long (argc
, argv
, "bno:JrVh", longopts
, NULL
)) != -1) {
463 for (ncolumns
= 0; ncolumns
< ARRAY_SIZE(infos
); ncolumns
++)
464 columns
[ncolumns
] = ncolumns
;
473 print_version(EXIT_SUCCESS
);
477 errtryhelp(EXIT_FAILURE
);
481 if (optind
== argc
) {
482 warnx(_("no file specified"));
483 errtryhelp(EXIT_FAILURE
);
487 columns
[ncolumns
++] = COL_RES
;
488 columns
[ncolumns
++] = COL_PAGES
;
489 columns
[ncolumns
++] = COL_SIZE
;
490 columns
[ncolumns
++] = COL_FILE
;
493 if (outarg
&& string_add_to_idarray(outarg
, columns
, ARRAY_SIZE(columns
),
494 &ncolumns
, column_name_to_id
) < 0)
498 ctl
.tb
= scols_new_table();
500 err(EXIT_FAILURE
, _("failed to allocate output table"));
502 scols_table_enable_noheadings(ctl
.tb
, ctl
.noheadings
);
503 scols_table_enable_raw(ctl
.tb
, ctl
.raw
);
504 scols_table_enable_json(ctl
.tb
, ctl
.json
);
506 scols_table_set_name(ctl
.tb
, "fincore");
508 for (i
= 0; i
< ncolumns
; i
++) {
509 const struct colinfo
*col
= get_column_info(i
);
510 struct libscols_column
*cl
;
512 cl
= scols_table_new_column(ctl
.tb
, col
->name
, col
->whint
, col
->flags
);
514 err(EXIT_FAILURE
, _("failed to allocate output column"));
517 int id
= get_column_id(i
);
521 scols_column_set_json_type(cl
, SCOLS_JSON_STRING
);
529 scols_column_set_json_type(cl
, SCOLS_JSON_NUMBER
);
535 for(; optind
< argc
; optind
++) {
536 struct fincore_state st
= {
537 .name
= argv
[optind
],
540 switch (fincore_name(&ctl
, &st
)) {
542 add_output_data(&ctl
, &st
);
552 scols_print_table(ctl
.tb
);
553 scols_unref_table(ctl
.tb
);