]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/fincore.c
lsblk: rename sortdata to rawdata
[thirdparty/util-linux.git] / misc-utils / fincore.c
1 /*
2 * fincore - count pages of file contents in core
3 *
4 * Copyright (C) 2017 Red Hat, Inc. All rights reserved.
5 * Written by Masatake YAMATO <yamato@redhat.com>
6 *
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.
11 *
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.
16 *
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.
20 */
21
22 #include <sys/mman.h>
23 #include <sys/stat.h>
24 #include <unistd.h>
25 #include <getopt.h>
26 #include <stdio.h>
27 #include <string.h>
28
29 #include "c.h"
30 #include "nls.h"
31 #include "closestream.h"
32 #include "xalloc.h"
33 #include "strutils.h"
34 #include "blkdev.h"
35
36 #include "libsmartcols.h"
37
38 /* For large files, mmap is called in iterative way.
39 Window is the unit of vma prepared in each mmap
40 calling.
41
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))
45
46 #ifndef HAVE_CACHESTAT
47
48 #ifndef SYS_cachestat
49 #define SYS_cachestat 451
50 #endif
51
52 struct cachestat_range {
53 uint64_t off;
54 uint64_t len;
55 };
56
57 struct cachestat {
58 uint64_t nr_cache;
59 uint64_t nr_dirty;
60 uint64_t nr_writeback;
61 uint64_t nr_evicted;
62 uint64_t nr_recently_evicted;
63 };
64
65 static inline int cachestat(unsigned int fd,
66 const struct cachestat_range *cstat_range,
67 struct cachestat *cstat, unsigned int flags)
68 {
69 return syscall(SYS_cachestat, fd, cstat_range, cstat, flags);
70 }
71
72 #endif // HAVE_CACHESTAT
73
74 struct colinfo {
75 const char * const name;
76 double whint;
77 int flags;
78 const char *help;
79 unsigned int pages : 1;
80 };
81
82 enum {
83 COL_PAGES,
84 COL_SIZE,
85 COL_FILE,
86 COL_RES,
87 COL_DIRTY_PAGES,
88 COL_DIRTY,
89 COL_WRITEBACK_PAGES,
90 COL_WRITEBACK,
91 COL_EVICTED_PAGES,
92 COL_EVICTED,
93 COL_RECENTLY_EVICTED_PAGES,
94 COL_RECENTLY_EVICTED,
95 };
96
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")},
110 };
111
112 static int columns[ARRAY_SIZE(infos) * 2] = {-1};
113 static size_t ncolumns;
114
115 struct fincore_control {
116 const size_t pagesize;
117
118 struct libscols_table *tb; /* output */
119
120 unsigned int bytes : 1,
121 noheadings : 1,
122 raw : 1,
123 json : 1;
124
125 };
126
127 struct fincore_state {
128 const char * const name;
129 long long unsigned int file_size;
130
131 struct cachestat cstat;
132 struct {
133 unsigned int dirty : 1,
134 writeback : 1,
135 evicted : 1,
136 recently_evicted : 1;
137 } cstat_fields;
138 };
139
140
141 static int column_name_to_id(const char *name, size_t namesz)
142 {
143 size_t i;
144
145 for (i = 0; i < ARRAY_SIZE(infos); i++) {
146 const char *cn = infos[i].name;
147
148 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
149 return i;
150 }
151 warnx(_("unknown column: %s"), name);
152 return -1;
153 }
154
155 static int get_column_id(int num)
156 {
157 assert(num >= 0);
158 assert((size_t) num < ncolumns);
159 assert(columns[num] < (int) ARRAY_SIZE(infos));
160 return columns[num];
161 }
162
163 static const struct colinfo *get_column_info(int num)
164 {
165 return &infos[ get_column_id(num) ];
166 }
167
168 static int get_cstat_value(const struct fincore_state *st, int column_id,
169 uint64_t *value)
170 {
171 switch(column_id) {
172 case COL_PAGES:
173 case COL_RES:
174 *value = st->cstat.nr_cache;
175 return 1;
176 case COL_DIRTY_PAGES:
177 case COL_DIRTY:
178 if (!st->cstat_fields.dirty)
179 break;
180 *value = st->cstat.nr_dirty;
181 return 1;
182 case COL_WRITEBACK_PAGES:
183 case COL_WRITEBACK:
184 if (!st->cstat_fields.writeback)
185 *value = st->cstat.nr_writeback;
186 return 1;
187 case COL_EVICTED_PAGES:
188 case COL_EVICTED:
189 if (!st->cstat_fields.evicted)
190 break;
191 *value = st->cstat.nr_evicted;
192 return 1;
193 case COL_RECENTLY_EVICTED_PAGES:
194 case COL_RECENTLY_EVICTED:
195 if (!st->cstat_fields.recently_evicted)
196 break;
197 *value = st->cstat.nr_recently_evicted;
198 return 1;
199 default:
200 assert(0);
201 }
202 return 0;
203 }
204
205 static int add_output_data(struct fincore_control *ctl,
206 struct fincore_state *st)
207 {
208 size_t i;
209 char *tmp;
210 uint64_t value = 0;
211 struct libscols_line *ln;
212
213 assert(ctl);
214 assert(ctl->tb);
215
216 ln = scols_table_new_line(ctl->tb, NULL);
217 if (!ln)
218 err(EXIT_FAILURE, _("failed to allocate output line"));
219
220 for (i = 0; i < ncolumns; i++) {
221 int rc = 0;
222 int column_id = get_column_id(i);
223 int format_value = 0;
224
225 switch(column_id) {
226 case COL_FILE:
227 rc = scols_line_set_data(ln, i, st->name);
228 break;
229 case COL_SIZE:
230 if (ctl->bytes)
231 xasprintf(&tmp, "%jd", (intmax_t) st->file_size);
232 else
233 tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, st->file_size);
234 rc = scols_line_refer_data(ln, i, tmp);
235 break;
236 case COL_PAGES:
237 case COL_RES:
238 case COL_DIRTY_PAGES:
239 case COL_DIRTY:
240 case COL_WRITEBACK_PAGES:
241 case COL_WRITEBACK:
242 case COL_EVICTED:
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);
247 break;
248 default:
249 return -EINVAL;
250 }
251
252 if (format_value) {
253 if (get_column_info(i)->pages) {
254 xasprintf(&tmp, "%ju", (uintmax_t) value);
255 } else {
256 value *= ctl->pagesize;
257 if (ctl->bytes)
258 xasprintf(&tmp, "%ju", (uintmax_t) value);
259 else
260 tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, value);
261 }
262 rc = scols_line_refer_data(ln, i, tmp);
263 }
264
265 if (rc)
266 err(EXIT_FAILURE, _("failed to add output data"));
267 }
268
269 return 0;
270 }
271
272 static int do_mincore(struct fincore_control *ctl,
273 void *window, const size_t len,
274 struct fincore_state *st)
275 {
276 static unsigned char vec[N_PAGES_IN_WINDOW];
277 int n = (len / ctl->pagesize) + ((len % ctl->pagesize)? 1: 0);
278
279 if (mincore (window, len, vec) < 0) {
280 warn(_("failed to do mincore: %s"), st->name);
281 return -errno;
282 }
283
284 while (n > 0)
285 {
286 if (vec[--n] & 0x1)
287 {
288 vec[n] = 0;
289 st->cstat.nr_cache++;
290 }
291 }
292
293 return 0;
294 }
295
296 static int mincore_fd (struct fincore_control *ctl,
297 int fd,
298 struct fincore_state *st)
299 {
300 size_t window_size = N_PAGES_IN_WINDOW * ctl->pagesize;
301 long long unsigned int file_offset, len;
302 int rc = 0;
303
304 for (file_offset = 0; file_offset < st->file_size; file_offset += len) {
305 void *window = NULL;
306
307 len = st->file_size - file_offset;
308 if (len >= window_size)
309 len = window_size;
310
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) {
314 rc = -EINVAL;
315 warn(_("failed to do mmap: %s"), st->name);
316 break;
317 }
318
319 rc = do_mincore(ctl, window, len, st);
320 if (rc)
321 break;
322
323 munmap (window, len);
324 }
325
326 return rc;
327 }
328
329 static int fincore_fd (struct fincore_control *ctl,
330 int fd,
331 struct fincore_state *st)
332 {
333 int rc;
334 const struct cachestat_range cstat_range = { 0 };
335
336 rc = cachestat(fd, &cstat_range, &st->cstat, 0);
337 if (!rc) {
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;
342 return 0;
343 }
344
345 if (errno != ENOSYS)
346 warn(_("failed to do cachestat: %s"), st->name);
347
348 return mincore_fd(ctl, fd, st);
349 }
350
351 /*
352 * Returns: <0 on error, 0 success, 1 ignore.
353 */
354 static int fincore_name(struct fincore_control *ctl,
355 struct fincore_state *st)
356 {
357 int fd;
358 int rc = 0;
359 struct stat sb;
360
361 if ((fd = open (st->name, O_RDONLY)) < 0) {
362 warn(_("failed to open: %s"), st->name);
363 return -errno;
364 }
365
366 if (fstat (fd, &sb) < 0) {
367 warn(_("failed to do fstat: %s"), st->name);
368 close (fd);
369 return -errno;
370 }
371 st->file_size = sb.st_size;
372
373 if (S_ISBLK(sb.st_mode)) {
374 rc = blkdev_get_size(fd, &st->file_size);
375 if (rc)
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;
379 } else {
380 rc = 1; /* ignore things like symlinks
381 * and directories*/
382 }
383
384 if (!rc)
385 rc = fincore_fd(ctl, fd, st);
386
387 close (fd);
388 return rc;
389 }
390
391 static void __attribute__((__noreturn__)) usage(void)
392 {
393 FILE *out = stdout;
394 size_t i;
395
396 fputs(USAGE_HEADER, out);
397 fprintf(out, _(" %s [options] file...\n"), program_invocation_short_name);
398
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);
406
407 fputs(USAGE_SEPARATOR, out);
408 fprintf(out, USAGE_HELP_OPTIONS(23));
409
410 fputs(USAGE_COLUMNS, out);
411
412 for (i = 0; i < ARRAY_SIZE(infos); i++)
413 fprintf(out, " %22s %s\n", infos[i].name, _(infos[i].help));
414
415 fprintf(out, USAGE_MAN_TAIL("fincore(1)"));
416
417 exit(EXIT_SUCCESS);
418 }
419
420 int main(int argc, char ** argv)
421 {
422 int c;
423 size_t i;
424 int rc = EXIT_SUCCESS;
425 char *outarg = NULL;
426
427 struct fincore_control ctl = {
428 .pagesize = getpagesize()
429 };
430
431 enum {
432 OPT_OUTPUT_ALL = CHAR_MAX + 1
433 };
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 },
444 };
445
446 setlocale(LC_ALL, "");
447 bindtextdomain(PACKAGE, LOCALEDIR);
448 textdomain(PACKAGE);
449 close_stdout_atexit();
450
451 while ((c = getopt_long (argc, argv, "bno:JrVh", longopts, NULL)) != -1) {
452 switch (c) {
453 case 'b':
454 ctl.bytes = 1;
455 break;
456 case 'n':
457 ctl.noheadings = 1;
458 break;
459 case 'o':
460 outarg = optarg;
461 break;
462 case OPT_OUTPUT_ALL:
463 for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++)
464 columns[ncolumns] = ncolumns;
465 break;
466 case 'J':
467 ctl.json = 1;
468 break;
469 case 'r':
470 ctl.raw = 1;
471 break;
472 case 'V':
473 print_version(EXIT_SUCCESS);
474 case 'h':
475 usage();
476 default:
477 errtryhelp(EXIT_FAILURE);
478 }
479 }
480
481 if (optind == argc) {
482 warnx(_("no file specified"));
483 errtryhelp(EXIT_FAILURE);
484 }
485
486 if (!ncolumns) {
487 columns[ncolumns++] = COL_RES;
488 columns[ncolumns++] = COL_PAGES;
489 columns[ncolumns++] = COL_SIZE;
490 columns[ncolumns++] = COL_FILE;
491 }
492
493 if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
494 &ncolumns, column_name_to_id) < 0)
495 return EXIT_FAILURE;
496
497 scols_init_debug(0);
498 ctl.tb = scols_new_table();
499 if (!ctl.tb)
500 err(EXIT_FAILURE, _("failed to allocate output table"));
501
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);
505 if (ctl.json)
506 scols_table_set_name(ctl.tb, "fincore");
507
508 for (i = 0; i < ncolumns; i++) {
509 const struct colinfo *col = get_column_info(i);
510 struct libscols_column *cl;
511
512 cl = scols_table_new_column(ctl.tb, col->name, col->whint, col->flags);
513 if (!cl)
514 err(EXIT_FAILURE, _("failed to allocate output column"));
515
516 if (ctl.json) {
517 int id = get_column_id(i);
518
519 switch (id) {
520 case COL_FILE:
521 scols_column_set_json_type(cl, SCOLS_JSON_STRING);
522 break;
523 case COL_SIZE:
524 case COL_RES:
525 if (!ctl.bytes)
526 break;
527 /* fallthrough */
528 default:
529 scols_column_set_json_type(cl, SCOLS_JSON_NUMBER);
530 break;
531 }
532 }
533 }
534
535 for(; optind < argc; optind++) {
536 struct fincore_state st = {
537 .name = argv[optind],
538 };
539
540 switch (fincore_name(&ctl, &st)) {
541 case 0:
542 add_output_data(&ctl, &st);
543 break;
544 case 1:
545 break; /* ignore */
546 default:
547 rc = EXIT_FAILURE;
548 break;
549 }
550 }
551
552 scols_print_table(ctl.tb);
553 scols_unref_table(ctl.tb);
554
555 return rc;
556 }