1 /*
2 * VMPQINACTIVE.C
3 *
4 * cc -I/usr/src/sys vmpqinactive.c -o ~/bin/vmpqinactive -lkvm
5 *
6 * vmpqinactive
7 *
8 * Calculate how many inactive pages are dirty
9 *
10 * Copyright (c) 2004-2020 The DragonFly Project. All rights reserved.
11 *
12 * This code is derived from software contributed to The DragonFly Project
13 * by Matthew Dillon <dillon@backplane.com>
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
17 * are met:
18 *
19 * 1. Redistributions of source code must retain the above copyright
20 * notice, this list of conditions and the following disclaimer.
21 * 2. Redistributions in binary form must reproduce the above copyright
22 * notice, this list of conditions and the following disclaimer in
23 * the documentation and/or other materials provided with the
24 * distribution.
25 * 3. Neither the name of The DragonFly Project nor the names of its
26 * contributors may be used to endorse or promote products derived
27 * from this software without specific, prior written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
32 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
33 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
34 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
35 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
36 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
37 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
38 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
39 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * SUCH DAMAGE.
41 */
42
43 #define _KERNEL_STRUCTURES_
44 #include <sys/param.h>
45 #include <sys/user.h>
46 #include <sys/malloc.h>
47 #include <sys/signalvar.h>
48 #include <sys/vnode.h>
49 #include <sys/buf.h>
50 #include <sys/namecache.h>
51 #include <sys/slaballoc.h>
52
53 #include <vm/vm.h>
54 #include <vm/vm_page.h>
55 #include <vm/vm_kern.h>
56 #include <vm/vm_object.h>
57 #include <vm/swap_pager.h>
58 #include <vm/vnode_pager.h>
59
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <fcntl.h>
64 #include <kvm.h>
65 #include <nlist.h>
66 #include <getopt.h>
67
68 struct nlist Nl[] = {
69 { "_vm_page_array" },
70 { "_vm_page_array_size" },
71 { "_kernel_object" },
72 { "_nbuf" },
73 { "_nswbuf_mem" },
74 { "_nswbuf_kva" },
75 { "_nswbuf_raw" },
76 { "_kernbase" },
77 { "__end" },
78 { NULL }
79 };
80
81 int debugopt;
82 int verboseopt;
83 #if 0
84 struct vm_page **vm_page_buckets;
85 int vm_page_hash_mask;
86 #endif
87 struct vm_page *vm_page_array;
88 struct vm_object *kernel_object_ptr;
89 int vm_page_array_size;
90 long nbuf;
91 long nswbuf_mem;
92 long nswbuf_kva;
93 long nswbuf_raw;
94 long kern_size;
95
96 void checkpage(kvm_t *kd, vm_page_t mptr, vm_page_t m, struct vm_object *obj);
97 static void kkread_vmpage(kvm_t *kd, u_long addr, vm_page_t m);
98 static void kkread(kvm_t *kd, u_long addr, void *buf, size_t nbytes);
99 static int kkread_err(kvm_t *kd, u_long addr, void *buf, size_t nbytes);
100
101 #if 0
102 static void addsltrack(vm_page_t m);
103 static void dumpsltrack(kvm_t *kd);
104 #endif
105 static int unique_object(void *ptr);
106
107 long count_free;
108 long count_wired; /* total */
109 long count_wired_vnode;
110 long count_wired_anon;
111 long count_wired_in_pmap;
112 long count_wired_pgtable;
113 long count_wired_other;
114 long count_wired_kernel;
115 long count_wired_obj_other;
116
117 long count_anon;
118 long count_anon_in_pmap;
119 long count_vnode;
120 long count_device;
121 long count_phys;
122 long count_kernel;
123 long count_unknown;
124 long count_noobj_offqueue;
125 long count_noobj_onqueue;
126
127 int
main(int ac,char ** av)128 main(int ac, char **av)
129 {
130 const char *corefile = NULL;
131 const char *sysfile = NULL;
132 struct vm_page m;
133 struct vm_object obj;
134 kvm_t *kd;
135 int ch;
136 #if 0
137 vm_page_t mptr;
138 int hv;
139 #endif
140 int i;
141 const char *qstr;
142 const char *ostr;
143 long pqinactive_clean;
144 long pqinactive_dirty1;
145 long pqinactive_dirty2;
146 long pqinactive_refd;
147 long pqinactive_ready;
148
149 pqinactive_clean = 0;
150 pqinactive_dirty1 = 0;
151 pqinactive_dirty2 = 0;
152 pqinactive_refd = 0;
153 pqinactive_ready = 0;
154
155 while ((ch = getopt(ac, av, "M:N:dv")) != -1) {
156 switch(ch) {
157 case 'd':
158 ++debugopt;
159 break;
160 case 'v':
161 ++verboseopt;
162 break;
163 case 'M':
164 corefile = optarg;
165 break;
166 case 'N':
167 sysfile = optarg;
168 break;
169 default:
170 fprintf(stderr, "%s [-M core] [-N system]\n", av[0]);
171 exit(1);
172 }
173 }
174 ac -= optind;
175 av += optind;
176
177 if ((kd = kvm_open(sysfile, corefile, NULL, O_RDONLY, "kvm:")) == NULL) {
178 perror("kvm_open");
179 exit(1);
180 }
181 if (kvm_nlist(kd, Nl) != 0) {
182 perror("kvm_nlist");
183 exit(1);
184 }
185
186 kkread(kd, Nl[0].n_value, &vm_page_array, sizeof(vm_page_array));
187 kkread(kd, Nl[1].n_value, &vm_page_array_size, sizeof(vm_page_array_size));
188 kernel_object_ptr = (void *)Nl[2].n_value;
189 kkread(kd, Nl[3].n_value, &nbuf, sizeof(nbuf));
190 kkread(kd, Nl[4].n_value, &nswbuf_mem, sizeof(nswbuf_mem));
191 kkread(kd, Nl[5].n_value, &nswbuf_kva, sizeof(nswbuf_kva));
192 kkread(kd, Nl[6].n_value, &nswbuf_raw, sizeof(nswbuf_raw));
193 kern_size = Nl[8].n_value - Nl[7].n_value;
194
195 /*
196 * Scan the vm_page_array validating all pages with associated objects
197 */
198 for (i = 0; i < vm_page_array_size; ++i) {
199 if (debugopt && (i & 1023) == 0) {
200 printf("page %d/%d\r", i, vm_page_array_size);
201 fflush(stdout);
202 }
203 kkread_vmpage(kd, (u_long)&vm_page_array[i], &m);
204 if (m.object) {
205 kkread(kd, (u_long)m.object, &obj, sizeof(obj));
206 checkpage(kd, &vm_page_array[i], &m, &obj);
207 }
208 if (m.queue >= PQ_HOLD) {
209 qstr = "HOLD";
210 } else if (m.queue >= PQ_CACHE) {
211 qstr = "CACHE";
212 } else if (m.queue >= PQ_ACTIVE) {
213 qstr = "ACTIVE";
214 } else if (m.queue >= PQ_INACTIVE) {
215 qstr = "INACTIVE";
216 if (m.dirty || m.wire_count || m.busy_count || m.hold_count ||
217 (m.flags & PG_NEED_COMMIT)) {
218 if (m.flags & PG_WINATCFLS)
219 ++pqinactive_dirty2;
220 else
221 ++pqinactive_dirty1;
222 } else {
223 ++pqinactive_clean;
224 if (m.flags & PG_REFERENCED)
225 ++pqinactive_refd;
226 else
227 ++pqinactive_ready;
228 }
229 } else if (m.queue >= PQ_FREE) {
230 qstr = "FREE";
231 ++count_free;
232 } else {
233 qstr = "NONE";
234 }
235 if (m.wire_count) {
236 ++count_wired;
237 if (m.object == NULL) {
238 if ((m.flags & PG_MAPPED) &&
239 (m.flags & PG_WRITEABLE) &&
240 (m.flags & PG_UNQUEUED)) {
241 ++count_wired_pgtable;
242 } else {
243 ++count_wired_other;
244 }
245 } else if (m.object == kernel_object_ptr) {
246 ++count_wired_kernel;
247 } else {
248 switch(obj.type) {
249 case OBJT_VNODE:
250 ++count_wired_vnode;
251 break;
252 case OBJT_DEFAULT:
253 case OBJT_SWAP:
254 if (m.flags & PG_MAPPED)
255 ++count_wired_in_pmap;
256 else
257 ++count_wired_anon;
258 break;
259 default:
260 ++count_wired_obj_other;
261 break;
262 }
263 }
264 } else
265 if (m.flags & PG_MAPPED) {
266 if (m.object && m.object != kernel_object_ptr) {
267 switch(obj.type) {
268 case OBJT_DEFAULT:
269 case OBJT_SWAP:
270 ++count_anon_in_pmap;
271 break;
272 default:
273 break;
274 }
275 }
276 }
277
278 if (verboseopt) {
279 printf("page %p obj %p/%-8ju(%016jx) val=%02x dty=%02x hold=%d "
280 "wire=%-2d act=%-3d busy=%d w/pmapcnt=%d/%d %8s",
281 &vm_page_array[i],
282 m.object,
283 (intmax_t)m.pindex,
284 (intmax_t)m.pindex * PAGE_SIZE,
285 m.valid,
286 m.dirty,
287 m.hold_count,
288 m.wire_count,
289 m.act_count,
290 m.busy_count,
291 ((m.flags & PG_WRITEABLE) != 0),
292 ((m.flags & PG_MAPPED) != 0),
293 qstr
294 );
295 }
296
297 if (m.object == kernel_object_ptr) {
298 ostr = "kernel";
299 if (unique_object(m.object))
300 count_kernel += obj.resident_page_count;
301 } else if (m.object) {
302 switch(obj.type) {
303 case OBJT_DEFAULT:
304 ostr = "default";
305 if (unique_object(m.object))
306 count_anon += obj.resident_page_count;
307 break;
308 case OBJT_SWAP:
309 ostr = "swap";
310 if (unique_object(m.object))
311 count_anon += obj.resident_page_count;
312 break;
313 case OBJT_VNODE:
314 ostr = "vnode";
315 if (unique_object(m.object))
316 count_vnode += obj.resident_page_count;
317 break;
318 case OBJT_DEVICE:
319 ostr = "device";
320 if (unique_object(m.object))
321 count_device += obj.resident_page_count;
322 break;
323 case OBJT_PHYS:
324 ostr = "phys";
325 if (unique_object(m.object))
326 count_phys += obj.resident_page_count;
327 break;
328 case OBJT_DEAD:
329 ostr = "dead";
330 if (unique_object(m.object))
331 count_unknown += obj.resident_page_count;
332 break;
333 default:
334 if (unique_object(m.object))
335 count_unknown += obj.resident_page_count;
336 ostr = "unknown";
337 break;
338 }
339 } else {
340 ostr = "-";
341 if (m.queue == PQ_NONE)
342 ++count_noobj_offqueue;
343 else if (m.queue - m.pc != PQ_FREE)
344 ++count_noobj_onqueue;
345 }
346
347 if (verboseopt) {
348 printf(" %-7s", ostr);
349 if (m.busy_count & PBUSY_LOCKED)
350 printf(" BUSY");
351 if (m.busy_count & PBUSY_WANTED)
352 printf(" WANTED");
353 if (m.flags & PG_WINATCFLS)
354 printf(" WINATCFLS");
355 if (m.flags & PG_FICTITIOUS)
356 printf(" FICTITIOUS");
357 if (m.flags & PG_WRITEABLE)
358 printf(" WRITEABLE");
359 if (m.flags & PG_MAPPED)
360 printf(" MAPPED");
361 if (m.flags & PG_NEED_COMMIT)
362 printf(" NEED_COMMIT");
363 if (m.flags & PG_REFERENCED)
364 printf(" REFERENCED");
365 if (m.flags & PG_CLEANCHK)
366 printf(" CLEANCHK");
367 if (m.busy_count & PBUSY_SWAPINPROG)
368 printf(" SWAPINPROG");
369 if (m.flags & PG_NOSYNC)
370 printf(" NOSYNC");
371 if (m.flags & PG_UNQUEUED)
372 printf(" UNQUEUED");
373 if (m.flags & PG_MARKER)
374 printf(" MARKER");
375 if (m.flags & PG_RAM)
376 printf(" RAM");
377 if (m.flags & PG_SWAPPED)
378 printf(" SWAPPED");
379 #if 0
380 if (m.flags & PG_SLAB)
381 printf(" SLAB");
382 #endif
383 printf("\n");
384 #if 0
385 if (m.flags & PG_SLAB)
386 addsltrack(&m);
387 #endif
388 }
389 }
390 if (debugopt || verboseopt)
391 printf("\n");
392 printf("%8.2fM free\n",
393 count_free * 4096.0 / 1048576.0);
394 printf("%8.2fM inactive-clean\n",
395 pqinactive_clean * 4096.0 / 1048576.0);
396 printf("%8.2fM inactive-clean-and-referenced\n",
397 pqinactive_refd * 4096.0 / 1048576.0);
398 printf("%8.2fM inactive-clean-and-ready\n",
399 pqinactive_ready * 4096.0 / 1048576.0);
400 printf("%8.2fM inactive-dirty/first-LRU\n",
401 pqinactive_dirty1 * 4096.0 / 1048576.0);
402 printf("%8.2fM inactive-dirty/second-LRU\n",
403 pqinactive_dirty2 * 4096.0 / 1048576.0);
404
405 printf("%8.2fM wired vnode (in buffer cache)\n",
406 count_wired_vnode * 4096.0 / 1048576.0);
407 printf("%8.2fM wired in-pmap (probably vnode pages also in buffer cache)\n",
408 count_wired_in_pmap * 4096.0 / 1048576.0);
409 printf("%8.2fM wired pgtable\n",
410 count_wired_pgtable * 4096.0 / 1048576.0);
411 printf("%8.2fM wired anon\n",
412 count_wired_anon * 4096.0 / 1048576.0);
413 printf("%8.2fM wired kernel_object\n",
414 count_wired_kernel * 4096.0 / 1048576.0);
415
416 printf("\t%8.2fM vm_page_array\n",
417 vm_page_array_size * sizeof(struct vm_page) / 1048576.0);
418 printf("\t%8.2fM buf, swbuf_mem, swbuf_kva, swbuf_raw\n",
419 (nbuf + nswbuf_mem + nswbuf_kva + nswbuf_raw) *
420 sizeof(struct buf) / 1048576.0);
421 printf("\t%8.2fM kernel binary\n", kern_size / 1048576.0);
422 printf("\t(also add in KMALLOC id kmapinfo, or loosely, vmstat -m)\n");
423
424 printf("%8.2fM wired other (unknown object)\n",
425 count_wired_obj_other * 4096.0 / 1048576.0);
426 printf("%8.2fM wired other (no object, probably kernel)\n",
427 count_wired_other * 4096.0 / 1048576.0);
428
429 printf("%8.2fM WIRED TOTAL\n",
430 count_wired * 4096.0 / 1048576.0);
431
432 printf("\n");
433 printf("%8.2fM anonymous (total, includes in-pmap)\n",
434 count_anon * 4096.0 / 1048576.0);
435 printf("%8.2fM anonymous memory in-pmap\n",
436 count_anon_in_pmap * 4096.0 / 1048576.0);
437 printf("%8.2fM vnode (includes wired)\n",
438 count_vnode * 4096.0 / 1048576.0);
439 printf("%8.2fM device\n", count_device * 4096.0 / 1048576.0);
440 printf("%8.2fM phys\n", count_phys * 4096.0 / 1048576.0);
441 printf("%8.2fM kernel (includes wired)\n",
442 count_kernel * 4096.0 / 1048576.0);
443 printf("%8.2fM unknown\n", count_unknown * 4096.0 / 1048576.0);
444 printf("%8.2fM no_object, off queue (includes wired w/o object)\n",
445 count_noobj_offqueue * 4096.0 / 1048576.0);
446 printf("%8.2fM no_object, on non-free queue (includes wired w/o object)\n",
447 count_noobj_onqueue * 4096.0 / 1048576.0);
448
449 #if 0
450 /*
451 * Scan the vm_page_buckets array validating all pages found
452 */
453 for (i = 0; i <= vm_page_hash_mask; ++i) {
454 if (debugopt) {
455 printf("index %d\r", i);
456 fflush(stdout);
457 }
458 kkread(kd, (u_long)&vm_page_buckets[i], &mptr, sizeof(mptr));
459 while (mptr) {
460 kkread(kd, (u_long)mptr, &m, sizeof(m));
461 if (m.object) {
462 kkread(kd, (u_long)m.object, &obj, sizeof(obj));
463 hv = ((uintptr_t)m.object + m.pindex) ^ obj.hash_rand;
464 hv &= vm_page_hash_mask;
465 if (i != hv)
466 printf("vm_page_buckets[%d] ((struct vm_page *)%p)"
467 " should be in bucket %d\n", i, mptr, hv);
468 checkpage(kd, mptr, &m, &obj);
469 } else {
470 printf("vm_page_buckets[%d] ((struct vm_page *)%p)"
471 " has no object\n", i, mptr);
472 }
473 mptr = m.hnext;
474 }
475 }
476 #endif
477 if (debugopt)
478 printf("\n");
479 #if 0
480 dumpsltrack(kd);
481 #endif
482 return(0);
483 }
484
485 /*
486 * A page with an object.
487 */
488 void
checkpage(kvm_t * kd,vm_page_t mptr,vm_page_t m,struct vm_object * obj)489 checkpage(kvm_t *kd, vm_page_t mptr, vm_page_t m, struct vm_object *obj)
490 {
491 #if 0
492 struct vm_page scan;
493 vm_page_t scanptr;
494 int hv;
495
496 hv = ((uintptr_t)m->object + m->pindex) ^ obj->hash_rand;
497 hv &= vm_page_hash_mask;
498 kkread(kd, (u_long)&vm_page_buckets[hv], &scanptr, sizeof(scanptr));
499 while (scanptr) {
500 if (scanptr == mptr)
501 break;
502 kkread(kd, (u_long)scanptr, &scan, sizeof(scan));
503 scanptr = scan.hnext;
504 }
505 if (scanptr) {
506 if (debugopt > 1)
507 printf("good checkpage %p bucket %d\n", mptr, hv);
508 } else {
509 printf("vm_page_buckets[%d] ((struct vm_page *)%p)"
510 " page not found in bucket list\n", hv, mptr);
511 }
512 #endif
513 }
514
515 /*
516 * Acclerate the reading of VM pages
517 */
518 #define VPCACHE_SIZE 65536
519
520 static void
kkread_vmpage(kvm_t * kd,u_long addr,vm_page_t m)521 kkread_vmpage(kvm_t *kd, u_long addr, vm_page_t m)
522 {
523 static struct vm_page vpcache[VPCACHE_SIZE];
524 static u_long vpbeg;
525 static u_long vpend;
526
527 if (addr < vpbeg || addr >= vpend) {
528 vpbeg = addr;
529 vpend = addr + VPCACHE_SIZE * sizeof(*m);
530 if (vpend > (u_long)(uintptr_t)vm_page_array +
531 vm_page_array_size * sizeof(*m)) {
532 vpend = (u_long)(uintptr_t)vm_page_array +
533 vm_page_array_size * sizeof(*m);
534 }
535 kkread(kd, vpbeg, vpcache, vpend - vpbeg);
536 }
537 *m = vpcache[(addr - vpbeg) / sizeof(*m)];
538 }
539
540 static void
kkread(kvm_t * kd,u_long addr,void * buf,size_t nbytes)541 kkread(kvm_t *kd, u_long addr, void *buf, size_t nbytes)
542 {
543 if (kvm_read(kd, addr, buf, nbytes) != nbytes) {
544 perror("kvm_read");
545 exit(1);
546 }
547 }
548
549 static int
kkread_err(kvm_t * kd,u_long addr,void * buf,size_t nbytes)550 kkread_err(kvm_t *kd, u_long addr, void *buf, size_t nbytes)
551 {
552 if (kvm_read(kd, addr, buf, nbytes) != nbytes) {
553 return 1;
554 }
555 return 0;
556 }
557
558 struct SLTrack {
559 struct SLTrack *next;
560 u_long addr;
561 };
562
563 #define SLHSIZE 1024
564 #define SLHMASK (SLHSIZE - 1)
565
566 struct SLTrack *SLHash[SLHSIZE];
567
568 #if 0
569 static
570 void
571 addsltrack(vm_page_t m)
572 {
573 struct SLTrack *slt;
574 u_long addr = (m->pindex * PAGE_SIZE) & ~131071L;
575 int i;
576
577 if (m->wire_count == 0 || (m->flags & PG_MAPPED) == 0 ||
578 m->object == NULL)
579 return;
580
581 i = (addr / 131072) & SLHMASK;
582 for (slt = SLHash[i]; slt; slt = slt->next) {
583 if (slt->addr == addr)
584 break;
585 }
586 if (slt == NULL) {
587 slt = malloc(sizeof(*slt));
588 slt->addr = addr;
589 slt->next = SLHash[i];
590 SLHash[i] = slt;
591 }
592 }
593 #endif
594
595 static
596 void
dumpsltrack(kvm_t * kd)597 dumpsltrack(kvm_t *kd)
598 {
599 struct SLTrack *slt;
600 int i;
601 long total_zones = 0;
602 long full_zones = 0;
603
604 for (i = 0; i < SLHSIZE; ++i) {
605 for (slt = SLHash[i]; slt; slt = slt->next) {
606 SLZone z;
607
608 if (kkread_err(kd, slt->addr, &z, sizeof(z))) {
609 printf("SLZone 0x%016lx not mapped\n",
610 slt->addr);
611 continue;
612 }
613 printf("SLZone 0x%016lx { mag=%08x cpu=%-2d NFree=%-3d "
614 "chunksz=%-5d }\n",
615 slt->addr,
616 z.z_Magic,
617 z.z_Cpu,
618 z.z_NFree,
619 z.z_ChunkSize
620 );
621 ++total_zones;
622 if (z.z_NFree == 0)
623 ++full_zones;
624 }
625 }
626 printf("FullZones/TotalZones: %ld/%ld\n", full_zones, total_zones);
627 }
628
629 #define HASH_SIZE (1024*1024)
630 #define HASH_MASK (HASH_SIZE - 1)
631
632 struct dup_entry {
633 struct dup_entry *next;
634 void *ptr;
635 };
636
637 struct dup_entry *dup_hash[HASH_SIZE];
638
639 static int
unique_object(void * ptr)640 unique_object(void *ptr)
641 {
642 struct dup_entry *hen;
643 int hv;
644
645 hv = (intptr_t)ptr ^ ((intptr_t)ptr >> 20);
646 hv &= HASH_MASK;
647 for (hen = dup_hash[hv]; hen; hen = hen->next) {
648 if (hen->ptr == ptr)
649 return 0;
650 }
651 hen = malloc(sizeof(*hen));
652 hen->next = dup_hash[hv];
653 hen->ptr = ptr;
654 dup_hash[hv] = hen;
655
656 return 1;
657 }
658