Home | History | Annotate | Line # | Download | only in fincore
      1 /*	$NetBSD: fincore.c,v 1.2 2024/11/03 10:43:27 rillig Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2011 YAMAMOTO Takashi,
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
     17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26  * SUCH DAMAGE.
     27  */
     28 
     29 /*
     30  * a utility to query which file pages are cached
     31  *
     32  * inspired by:
     33  * 	http://net.doit.wisc.edu/~plonka/fincore/
     34  * 	http://www.usenix.org/events/lisa07/tech/plonka.html
     35  */
     36 
     37 #include <sys/cdefs.h>
     38 #if defined(__NetBSD__)
     39 #ifndef lint
     40 __RCSID("$NetBSD: fincore.c,v 1.2 2024/11/03 10:43:27 rillig Exp $");
     41 #endif /* not lint */
     42 #endif /* defined(__NetBSD__) */
     43 
     44 #include <sys/param.h>
     45 #include <sys/stat.h>
     46 #include <sys/mman.h>
     47 
     48 #include <err.h>
     49 #include <fcntl.h>
     50 #include <stdbool.h>
     51 #include <stdio.h>
     52 #include <stdint.h>
     53 #include <stdlib.h>
     54 #include <string.h>
     55 #include <unistd.h>
     56 
     57 #if !defined(__arraycount)
     58 #define	__arraycount(a)	(sizeof(a)/sizeof(*a))
     59 #endif /* !defined(__arraycount) */
     60 
     61 size_t page_size;
     62 bool do_summary;
     63 bool be_quiet;
     64 
     65 /*
     66  * fincore: query which pages of the file are in-core.
     67  *
     68  * this function is intended to be compatible with:
     69  * 	http://lwn.net/Articles/371538/
     70  * 	http://libprefetch.cs.ucla.edu/
     71  *
     72  * while this can be implemented in kernel much more efficiently, i'm not
     73  * sure if making this a syscall in 2011 is a good idea.  this API does not
     74  * seem scalable for sparsely cached huge files.  the expected scalability
     75  * has been changed since the time when mincore was invented.
     76  *
     77  * some references:
     78  * 	http://wiki.postgresql.org/images/a/a2/Pgfincore_pgday10.pdf
     79  */
     80 
     81 static int
     82 fincore(int fd, off_t startoff, off_t endoff, unsigned char *vec)
     83 {
     84 	off_t off;
     85 	size_t chunk_size;
     86 
     87 	for (off = startoff; off < endoff;
     88 	    off += chunk_size, vec += chunk_size / page_size) {
     89 		void *vp;
     90 
     91 		chunk_size = MIN((off_t)(1024 * page_size), endoff - off);
     92 		vp = mmap(NULL, chunk_size, PROT_NONE, MAP_FILE|MAP_SHARED,
     93 		    fd, off);
     94 		if (vp == MAP_FAILED) {
     95 			return -1;
     96 		}
     97 		if (mincore(vp, chunk_size,
     98 #if !defined(__linux__)
     99 		    (char *)
    100 #endif /* !defined(__linux__) */
    101 		    vec)) {
    102 			munmap(vp, chunk_size);
    103 			return -1;
    104 		}
    105 		if (munmap(vp, chunk_size)) {
    106 			return -1;
    107 		}
    108 	}
    109 	return 0;
    110 }
    111 
    112 static void
    113 do_file(const char *name)
    114 {
    115 	unsigned char vec[4096];
    116 	struct stat st;
    117 	uintmax_t n;	/* number of pages in-core */
    118 	off_t off;
    119 	size_t chunk_size;
    120 	int fd;
    121 	bool header_done = false;
    122 
    123 	fd = open(name, O_RDONLY);
    124 	if (fd == -1) {
    125 		err(EXIT_FAILURE, "open %s", name);
    126 	}
    127 	if (fstat(fd, &st)) {
    128 		err(EXIT_FAILURE, "fstat %s", name);
    129 	}
    130 	n = 0;
    131 	for (off = 0; off < st.st_size; off += chunk_size) {
    132 		unsigned int i;
    133 
    134 		chunk_size = MIN(__arraycount(vec) * page_size,
    135 		    roundup(st.st_size - off, page_size));
    136 		if (fincore(fd, off, off + chunk_size, vec)) {
    137 			printf("\n");
    138 			err(EXIT_FAILURE, "fincore %s", name);
    139 		}
    140 		for (i = 0; i < chunk_size / page_size; i++) {
    141 			if (vec[i] == 0) {
    142 				continue;
    143 			}
    144 			if (!do_summary) {
    145 				if (!header_done) {
    146 					printf("%s:", name);
    147 					header_done = true;
    148 				}
    149 				printf(" %ju",
    150 				    (uintmax_t)(off / page_size + i));
    151 			}
    152 			n++;
    153 		}
    154 	}
    155 	close(fd);
    156 	if (do_summary && (n != 0 || !be_quiet)) {
    157 		const uintmax_t total = howmany(st.st_size, page_size);
    158 		const double pct = (total != 0) ? ((double)n / total * 100) : 0;
    159 
    160 		if (!header_done) {
    161 			printf("%s:", name);
    162 			header_done = true;
    163 		}
    164 		printf(" %ju / %ju in-core pages (%0.2f%%)", n, total, pct);
    165 	}
    166 	if (header_done) {
    167 		printf("\n");
    168 	} else if (!be_quiet) {
    169 		printf("%s: \n", name);
    170 	}
    171 }
    172 
    173 int
    174 /*ARGSUSED*/
    175 main(int argc, char *argv[])
    176 {
    177 	long l;
    178 	int ch;
    179 
    180 	while ((ch = getopt(argc, argv, "sq")) != -1) {
    181 		switch (ch) {
    182 		case 's':
    183 			do_summary = true;
    184 			break;
    185 		case 'q':
    186 			be_quiet = true;
    187 			break;
    188 		default:
    189 			exit(EXIT_FAILURE);
    190 		}
    191 	}
    192 
    193 	l = sysconf(_SC_PAGESIZE);
    194 	if (l == -1) {
    195 		/*
    196 		 * sysconf doesn't always set errno.  bad API.
    197 		 */
    198 		errx(EXIT_FAILURE, "_SC_PAGESIZE");
    199 	}
    200 	page_size = (size_t)l;
    201 
    202 	argc -= optind;
    203 	argv += optind;
    204 	while (argc > 0) {
    205 		do_file(argv[0]);
    206 		argc--;
    207 		argv++;
    208 	}
    209 	return EXIT_SUCCESS;
    210 }
    211