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
fincore(int fd,off_t startoff,off_t endoff,unsigned char * vec)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
do_file(const char * name)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*/
main(int argc,char * argv[])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