Why does host_statistics64() in OS X 10.6.8 (I don't know if other versions have this problem) return counts for free, active, inactive, and wired memory that don't add up to the total amount of ram? And why is it missing an inconsistent number of pages?
The following output represents the number of pages not classified as free, active, inactive, or wired over ten seconds (sampled roughly once per second).
458
243
153
199
357
140
304
93
181
224
The code that produces the numbers above is:
#include <stdio.h>
#include <mach/mach.h>
#include <mach/vm_statistics.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char** argv) {
struct vm_statistics64 stats;
mach_port_t host = mach_host_self();
natural_t count = HOST_VM_INFO64_COUNT;
natural_t missing = 0;
int debug = argc == 2 ? !strcmp(argv[1], "-v") : 0;
kern_return_t ret;
int mib[2];
long ram;
natural_t pages;
size_t length;
int i;
mib[0] = CTL_HW;
mib[1] = HW_MEMSIZE;
length = sizeof(long);
sysctl(mib, 2, &ram, &length, NULL, 0);
pages = ram / getpagesize();
for (i = 0; i < 10; i++) {
if ((ret = host_statistics64(host, HOST_VM_INFO64, (host_info64_t)&stats, &count)) != KERN_SUCCESS) {
printf("oops\n");
return 1;
}
/* updated for 10.9 */
missing = pages - (
stats.free_count +
stats.active_count +
stats.inactive_count +
stats.wire_count +
stats.compressor_page_count
);
if (debug) {
printf(
"%11d pages (# of pages)\n"
"%11d free_count (# of pages free) \n"
"%11d active_count (# of pages active) \n"
"%11d inactive_count (# of pages inactive) \n"
"%11d wire_count (# of pages wired down) \n"
"%11lld zero_fill_count (# of zero fill pages) \n"
"%11lld reactivations (# of pages reactivated) \n"
"%11lld pageins (# of pageins) \n"
"%11lld pageouts (# of pageouts) \n"
"%11lld faults (# of faults) \n"
"%11lld cow_faults (# of copy-on-writes) \n"
"%11lld lookups (object cache lookups) \n"
"%11lld hits (object cache hits) \n"
"%11lld purges (# of pages purged) \n"
"%11d purgeable_count (# of pages purgeable) \n"
"%11d speculative_count (# of pages speculative (also counted in free_count)) \n"
"%11lld decompressions (# of pages decompressed) \n"
"%11lld compressions (# of pages compressed) \n"
"%11lld swapins (# of pages swapped in (via compression segments)) \n"
"%11lld swapouts (# of pages swapped out (via compression segments)) \n"
"%11d compressor_page_count (# of pages used by the compressed pager to hold all the compressed data) \n"
"%11d throttled_count (# of pages throttled) \n"
"%11d external_page_count (# of pages that are file-backed (non-swap)) \n"
"%11d internal_page_count (# of pages that are anonymous) \n"
"%11lld total_uncompressed_pages_in_compressor (# of pages (uncompressed) held within the compressor.) \n",
pages, stats.free_count, stats.active_count, stats.inactive_count,
stats.wire_count, stats.zero_fill_count, stats.reactivations,
stats.pageins, stats.pageouts, stats.faults, stats.cow_faults,
stats.lookups, stats.hits, stats.purges, stats.purgeable_count,
stats.speculative_count, stats.decompressions, stats.compressions,
stats.swapins, stats.swapouts, stats.compressor_page_count,
stats.throttled_count, stats.external_page_count,
stats.internal_page_count, stats.total_uncompressed_pages_in_compressor
);
}
printf("%i\n", missing);
sleep(1);
}
return 0;
}
Just noticed that if you add
compressor_page_count
into the mix you get much closer to the actual amount of RAM in the machine.This is an observation, not an explanation, and links to where this was properly documented would be nice to have!
TL;DR:
host_statistics64()
get information from different sources which might cost time and could produce inconsistent results.host_statistics64()
gets some information by variables with names likevm_page_foo_count
. But not all of these variables are taken into account, e.g.vm_page_stolen_count
is not./usr/bin/top
adds stolen pages to the number of wired pages. This is an indicator that these pages should be taken into account when counting pages.Notes
/usr/bin/vm_stat
which is just a wrapper forhost_statistics64()
(andhost_statistics()
). The corressponding source code can be found here: system_cmds-496/vm_stat.tproj/vm_stat.c.How does
host_statistics64()
fit into XNU and how does it work?As widley know the OS X kernel is called XNU (XNU IS NOT UNIX) and "is a hybrid kernel combining the Mach kernel developed at Carnegie Mellon University with components from FreeBSD and C++ API for writing drivers called IOKit." (https://github.com/opensource-apple/xnu/blob/10.12/README.md)
The virtual memory management (VM) is part of Mach therefore
host_statistics64()
is located here. Let's have a closer look at the its implementation which is contained in xnu-3789.51.2/osfmk/kern/host.c.The function signature is
The first relevant lines are
We get
host_vm_stat
which is of typevm_statistics64_data_t
. This is just atypedef struct vm_statistics64
as you can see in xnu-3789.51.2/osfmk/mach/vm_statistics.h. And we get processor information from the makroPROCESSOR_DATA()
defined in xnu-3789.51.2/osfmk/kern/processor_data.h. We fillhost_vm_stat
while looping through all of our processors by simply adding up the relevant numbers.As you can see we find some well known stats like
zero_fill_count
orcompressions
but not all covered byhost_statistics64()
.The next relevant lines are:
We reuse
stat
and make it our output struct. We then fillfree_count
with the sum of twounsigned long
calledvm_page_free_count
andvm_page_speculative_count
. We collect the other remaining data in the same manner (by using variables namedvm_page_foo_count
) or by taking the stats fromhost_vm_stat
which we filled up above.1. Conclusion We collect data from different sources. Either from processor informations or from variables called
vm_page_foo_count
. This costs time and might end in some inconsitency matter the fact VM is a very fast and continous process.Let's take a closer look at the already mentioned variables
vm_page_foo_count
. They are defined in xnu-3789.51.2/osfmk/vm/vm_page.h as follows:That's a lot of statistics regarding we only get access to a very limited number using
host_statistics64()
. The most of these stats are updated in xnu-3789.51.2/osfmk/vm/vm_resident.c. For example this function releases pages to the list of free pages:Very interesting is
extern unsigned int vm_page_stolen_count; /* Count of stolen pages not acccounted in zones */
. What are stolen pages? It seems like there are mechanisms to take a page out of some lists even though it wouldn't usually be paged out. One of these mechanisms is the age of a page in the speculative page list. xnu-3789.51.2/osfmk/vm/vm_page.h tells usIt is indeed
void vm_pageout_scan(void)
that incrementsvm_page_stolen_count
. You find the corresponding source code in xnu-3789.51.2/osfmk/vm/vm_pageout.c.I think stolen pages are not taken into account while calculating VM stats a
host_statistics64()
does.Evidence that I'm right
The best way to prove this would be to compile XNU with an customized version of
host_statistics64()
by hand. I had no opportunity do this but will try soon.Fortunately we are not the only ones interested in correct VM statistics. Therefore we should have a look at the implementation of well know
/usr/bin/top
(not contained in XNU) which is completely available here: top-108 (I just picked the macOS 10.12.4 release).Let's have a look at top-108/libtop.c where we find the following:
tsamp
is of typelibtop_tsamp_t
which is a struct defined in top-108/libtop.h. It contains amongst other thingsvm_statistics64_data_t vm_stat
anduint64_t pages_stolen
.As you can see,
static int libtop_tsamp_update_vm_stats(libtop_tsamp_t* tsamp)
getstsamp->vm_stat
filled byhost_statistics64()
as we know it. Afterwards it checks iftsamp->pages_stolen > 0
and adds it up to thewire_count
field oftsamp->vm_stat
.2. Conclusion We won't get the number of these stolen pages if we just use
host_statistics64()
as in/usr/bin/vm_stat
or your example code!Why is
host_statistics64()
implemented as it is?Honestly, I don't know. Paging is a complex process and therefore a real time observation a challenging task. We have to notice that there seems to be no bug in its implementation. I think that we wouldn't even get a 100% accurate number of pages if we could get access to
vm_page_stolen_count
. The implementation of/usr/bin/top
doesn't count stolen pages if their number is not very big.An additional interesting thing is a comment above the function
static void update_pages_stolen(libtop_tsamp_t *tsamp)
which is/* This is for <rdar://problem/6410098>. */
. Open Radar is a bug reporting site for Apple software and usually classifies bugs in the format given in the comment. I was unable to find the related bug; maybe it was about missing pages.I hope these information could help you a bit. If I manage to compile the latest (and customized) Version of XNU on my machine I will let you know. Maybe this brings interesting insights.