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 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 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 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 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 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 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 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