LIBMEMSTAT(3)

๐Ÿ“ฆ version 15.1, in freebsd 14.5, freebsd 15.1

okay so bestie, welcome to libmemstat(3) ๐Ÿ’– โ€” yes that one, the page from the Library Functions Manual, and she is literally the group chat between your program and the kernel's memory allocators. no cap, this thing slaps. let's get into it โœจ


โœจ NAME โœจ

libmemstat โ€” library interface to retrieve kernel memory allocator statistics ๐ŸŽ€

like, that's her whole job. she goes and gets the numbers, you stay comfy. periodt.

๐Ÿ“š LIBRARY ๐Ÿ“š

Kernel Memory Allocator Statistics Library (libmemstat, -lmemstat) ๐Ÿฆ‹

yeah, that's the library name and the linker flag, they're matching, it's giving coordinated outfit ๐Ÿ”ฅ

๐ŸŒˆ SYNOPSIS ๐ŸŒˆ

okay here's the doorway, bestie, you gotta walk through it first:

#include <sys/types.h>
#include <memstat.h>

both of them. yes both. don't skip the first one she gets moody ๐ŸŒธ


๐Ÿซถ General Functions ๐Ÿซถ

const char *
memstat_strerror(int error);

one function, one job: you hand her an int error, she hands you back a const char *. iconic, efficient, no notes ๐Ÿ’…


๐Ÿ’… Memory Type List Management Functions ๐Ÿ’…

this whole crew is about the list โ€” making it, strolling through it, finding stuff in it, yeeting it, and asking it how it's feeling:

struct memory_type_list *
memstat_mtl_alloc(void);

struct memory_type *
memstat_mtl_first(struct memory_type_list *list);

struct memory_type *
memstat_mtl_next(struct memory_type *mtp);

struct memory_type *
memstat_mtl_find(struct memory_type_list *list, int allocator,
    const char *name);

void
memstat_mtl_free(struct memory_type_list *list);

int
memstat_mtl_geterror(struct memory_type_list *list);

see how memstat_mtl_alloc and memstat_mtl_first and memstat_mtl_next and memstat_mtl_find all return a struct memory_type * (well, struct memory_type_list * for the alloc one, she's special) โ€” and memstat_mtl_free returns void because she takes rather than gives ๐Ÿซถ

also ngl memstat_mtl_find is the main character here โ€” she wants a list, an allocator, and a name, miss girl is thorough. ๐Ÿ‘‘


๐Ÿ”ฅ Allocator Query Functions ๐Ÿ”ฅ

six functions, all returning int, and they come in two flavours โ€” the kvm girlies and the sysctl girlies:

int
memstat_kvm_all(struct memory_type_list *list, void *kvm_handle);

int
memstat_kvm_malloc(struct memory_type_list *list, void *kvm_handle);

int
memstat_kvm_uma(struct memory_type_list *list, void *kvm_handle);

int
memstat_sysctl_all(struct memory_type_list *list, int flags);

int
memstat_sysctl_malloc(struct memory_type_list *list, int flags);

int
memstat_sysctl_uma(struct memory_type_list *list, int flags);

notice the pattern bestie โ€” every single one wants a struct memory_type_list *list first. then the kvm ones want a void *kvm_handle and the sysctl ones want an int flags. the way that they all match is lowkey satisfying โœจ and yes, all, malloc and uma each get their own version in both families, she's consistent ๐Ÿ’œ


๐Ÿงš Memory Type Accessor Methods ๐Ÿงš

okay this is the big one, hydrate before you read it ๐Ÿ’– literally the entire getter wardrobe:

const char *
memstat_get_name(const struct memory_type *mtp);

int
memstat_get_allocator(const struct memory_type *mtp);

uint64_t
memstat_get_countlimit(const struct memory_type *mtp);

uint64_t
memstat_get_byteslimit(const struct memory_type *mtp);

uint64_t
memstat_get_sizemask(const struct memory_type *mtp);

uint64_t
memstat_get_size(const struct memory_type *mtp);

uint64_t
memstat_get_rsize(const struct memory_type *mtp);

uint64_t
memstat_get_memalloced(const struct memory_type *mtp);

uint64_t
memstat_get_memfreed(const struct memory_type *mtp);

uint64_t
memstat_get_numallocs(const struct memory_type *mtp);

uint64_t
memstat_get_numfrees(const struct memory_type *mtp);

uint64_t
memstat_get_bytes(const struct memory_type *mtp);

uint64_t
memstat_get_count(const struct memory_type *mtp);

uint64_t
memstat_get_free(const struct memory_type *mtp);

uint64_t
memstat_get_failures(const struct memory_type *mtp);

void *
memstat_get_caller_pointer(const struct memory_type *mtp, int index);

void
memstat_set_caller_pointer(struct memory_type *mtp, int index,
    void *value);

uint64_t
memstat_get_caller_uint64(const struct memory_type *mtp, int index);

void
memstat_set_caller_uint64(struct memory_type *mtp, int index,
    uint64_t value);

uint64_t
memstat_get_zonefree(const struct memory_type *mtp);

uint64_t
memstat_get_kegfree(const struct memory_type *mtp);

uint64_t
memstat_get_percpu_memalloced(const struct memory_type *mtp, int cpu);

uint64_t
memstat_get_percpu_memfreed(const struct memory_type *mtp, int cpu);

uint64_t
memstat_get_percpu_numallocs(const struct memory_type *mtp, int cpu);

uint64_t
memstat_get_percpu_numfrees(const struct memory_type *mtp, int cpu);

uint64_t
memstat_get_percpu_sizemask(const struct memory_type *mtp, int cpu);

void *
memstat_get_percpu_caller_pointer(const struct memory_type *mtp,
    int cpu, int index);

void
memstat_set_percpu_caller_pointer(struct memory_type *mtp, int cpu,
    int index, void *value);

uint64_t
memstat_get_percpu_caller_uint64(const struct memory_type *mtp, int cpu,
    int index);

void
memstat_set_percpu_caller_uint64(struct memory_type *mtp, int cpu,
    int index, uint64_t value);

uint64_t
memstat_get_percpu_free(const struct memory_type *mtp, int cpu);

okay let's break down the lore ๐Ÿ”

  • the plain getters all take just a const struct memory_type *mtp and most of them return uint64_t. the exceptions are memstat_get_name, which returns const char *, and memstat_get_allocator, which returns int. the rest of the fam: memstat_get_countlimit, memstat_get_byteslimit, memstat_get_sizemask, memstat_get_size, memstat_get_rsize, memstat_get_memalloced, memstat_get_memfreed, memstat_get_numallocs, memstat_get_numfrees, memstat_get_bytes, memstat_get_count, memstat_get_free, memstat_get_failures, memstat_get_zonefree, memstat_get_kegfree โ€” all uint64_t, all just mtp. ๐Ÿ’…
  • the caller ones bring a plus-one: they're memstat_get_caller_pointer and memstat_get_caller_uint64 taking (const struct memory_type *mtp, int index) โ€” and the void * return on memstat_get_caller_pointer is a whole different vibe from the uint64_t on the other. ๐Ÿ™ˆ
  • the setters exist too, bestie โ€” memstat_set_caller_pointer and memstat_set_caller_uint64, both void, both taking a non-const struct memory_type *mtp (she's mutable, she's getting written to, respect it) plus int index and the void *value / uint64_t value respectively. ๐Ÿ˜ญ
  • the per-cpu girlies are all of that again but with int cpu squeezed in, and they're the ones with _percpu_ right there in the name: memstat_get_percpu_memalloced, memstat_get_percpu_memfreed, memstat_get_percpu_numallocs, memstat_get_percpu_numfrees, memstat_get_percpu_sizemask, memstat_get_percpu_free (all uint64_t), plus memstat_get_percpu_caller_pointer returning void * and taking (const struct memory_type *mtp, int cpu, int index). and yes the setters are back: memstat_set_percpu_caller_pointer and memstat_set_percpu_caller_uint64, both void, both non-const mtp, both (struct memory_type *mtp, int cpu, int index, โ€ฆ) with void *value and uint64_t value ๐ŸŽ€

it's giving a lot of getters, and honestly? obsessed. every single stat you could want has her own little door. no cap. ๐Ÿ”ฅ


and that's the whole synopsis, bestie โ€” the actual prose is up next, so don't go anywhere ๐Ÿ’œ

๐Ÿ’ซ DESCRIPTION ๐Ÿ’ซ

okay bestie, so libmemstat is the library that gives you kernel memory allocator stats ๐Ÿซถ. It exists for debugging and system monitoring, it keeps applications insulated from the implementation details of the allocators, and it lets a tool transparently support multiple allocators. No cap. It can grab a single statistics snapshot, and it can also incrementally update stats for long-term monitoring.

libmemstat describes each memory type with a struct memory_type, an opaque memory type that your app touches through accessor functions in the library. The library returns and updates chains of struct memory_type through a struct memory_type_list, which gets allocated by calling memstat_mtl_alloc() and freed when youโ€™re done using memstat_mtl_free(). Lists of memory types get populated by calls that query the kernel for statistics information; currently: memstat_kvm_all(), memstat_kvm_malloc(), memstat_kvm_uma(), memstat_sysctl_all(), memstat_sysctl_uma(), and memstat_sysctl_malloc(). Repeated calls will incrementally update the list of memory types, so you can track stuff over time without recreating all list state. Periodt. If an error is detected during a query call, you can retrieve error condition information with memstat_mtl_geterror(), and convert it into a user-readable string with memstat_strerror().

๐Ÿ’€ Freeing the list frees all memory type data in the list, and so invalidates any outstanding pointers to entries in the list. Do not be out here holding stale pointers, bestie.

struct memory_type entries in the list may be iterated over using memstat_mtl_first() and memstat_mtl_next(), which respectively return the first entry in a list and the next entry in a list. Then thereโ€™s memstat_mtl_find(), which returns a pointer to the first entry matching the passed parameters. Slay.

A series of accessor methods is provided to access fields of the structure, including retrieving statistics and properties, as well as setting caller-owned fields. Direct application access to the data structure fields is not supported. So bestie, use the accessors โ€” do not rawdog the struct fields. ๐Ÿ™ˆ

๐Ÿง  Library memory_type Fields ๐Ÿง 

Each struct memory_type holds a description of the memory type, including its name and the allocator it is managed by, plus current statistics on use. Some statistics are directly measured, and others are derived from directly measured statistics. Certain high-level statistics are present across all available allocators, like the number of allocation and free operations; other measurements โ€” such as the quantity of free items in per-CPU caches, or the administrative limit on the number of allocations โ€” are available only for specific allocators.

๐ŸŽ€ Caller memory_type Fields ๐ŸŽ€

struct memory_type includes fields that let the application store data, in the form of pointers and 64-bit integers, with memory types. For example, the application author might use one of the caller pointers to reference a more complex data structure tracking long-term behavior of the memory type, or a window system object that is used to render the state of the memory type. General and per-CPU storage is provided with each struct memory_type in the form of an array of pointers and integers. The array entries are accessed via the index argument to the get and set accessor methods. Possible values of index range between 0 and MEMSTAT_MAXCALLER.

Caller-owned fields are initialized to 0 or NULL when a new struct memory_type is allocated and attached to a memory type list; these fields retain their values across queries that update library-owned fields. Iconic behavior, honestly. โœจ

๐ŸŒ€ Allocator Types ๐ŸŒ€

Currently, libmemstat supports two kernel allocators: ALLOCATOR_UMA for uma(9), and ALLOCATOR_MALLOC for malloc(9). These values may be passed to memstat_mtl_find(), and will be returned by memstat_get_allocator(). Two additional constants in the allocator namespace are defined: ALLOCATOR_UNKNOWN, which will only be returned as a result of a library error, and ALLOCATOR_ANY, which can be used to specify that returning types matching any allocator is permittable from memstat_mtl_find(). Iykyk.

๐Ÿ”ฎ Access Method List ๐Ÿ”ฎ

The following accessor methods are defined, of which some will be valid for a given memory type:

  • memstat_get_name() โ€” Return a pointer to the name of the memory type. Memory for the name is owned by libmemstat and will be valid through a call to memstat_mtl_free(). Note that names will be unique with respect to a single allocator, but the same name might be used by different memory types owned by different memory allocators.
  • memstat_get_allocator() โ€” Return an integer identifier for the memory allocator that owns the memory type.
  • memstat_get_countlimit() โ€” If the memory type has an administrative limit on the number of simultaneous allocations, return it.
  • memstat_get_byteslimit() โ€” If the memory type has an administrative limit on the number of bytes of memory that may be simultaneously allocated for the memory type, return it.
  • memstat_get_sizemask() โ€” If the memory type supports variable allocation sizes, return a bitmask of sizes allocated for the memory type.
  • memstat_get_size() โ€” If the memory type supports a fixed allocation size, return that size.
  • memstat_get_rsize() โ€” If the memory type supports a fixed allocation size, return real size of an allocation. Real size can exceed requested size due to alignment constraints or implicit padding.
  • memstat_get_memalloced() โ€” Return the total number of bytes allocated for the memory type over its lifetime.
  • memstat_get_memfreed() โ€” Return the total number of bytes freed for the memory type over its lifetime.
  • memstat_get_numallocs() โ€” Return the total number of allocations for the memory type over its lifetime.
  • memstat_get_numfrees() โ€” Return the total number of frees for the memory type over its lifetime.
  • memstat_get_bytes() โ€” Return the current number of bytes allocated to the memory type.
  • memstat_get_count() โ€” Return the current number of allocations for the memory type.
  • memstat_get_free() โ€” If the memory allocator supports a cache, return the number of items in the cache.
  • memstat_get_failures() โ€” If the memory allocator and type permit allocation failures, return the number of allocation failures measured.
  • memstat_get_caller_pointer() โ€” Return a caller-owned pointer for the memory type.
  • memstat_set_caller_pointer() โ€” Set a caller-owned pointer for the memory type.
  • memstat_get_caller_uint64() โ€” Return a caller-owned integer for the memory type.
  • memstat_set_caller_uint64() โ€” Set a caller-owned integer for the memory type.
  • memstat_get_zonefree() โ€” If the memory allocator supports a multi-level allocation structure, return the number of cached items in the zone. These items will be in a fully constructed state available for immediate use.
  • memstat_get_kegfree() โ€” If the memory allocator supports a multi-level allocation structure, return the number of cached items in the keg. These items may be in a partially constructed state, and may require further processing before they can be made available for use.
  • memstat_get_percpu_memalloced() โ€” If the memory allocator supports per-CPU statistics, return the number of bytes of memory allocated for the memory type on the CPU over its lifetime.
  • memstat_get_percpu_memfreed() โ€” If the memory allocator supports per-CPU statistics, return the number of bytes of memory freed from the memory type on the CPU over its lifetime.
  • memstat_get_percpu_numallocs() โ€” If the memory allocator supports per-CPU statistics, return the number of allocations for the memory type on the CPU over its lifetime.
  • memstat_get_percpu_numfrees() โ€” If the memory allocator supports per-CPU statistics, return the number of frees for the memory type on the CPU over its lifetime.
  • memstat_get_percpu_sizemask() โ€” If the memory allocator supports variable size memory allocation and per-CPU statistics, return the size bitmask for the memory type on the CPU.
  • memstat_get_percpu_caller_pointer() โ€” Return a caller-owned per-CPU pointer for the memory type.
  • memstat_set_percpu_caller_pointer() โ€” Set a caller-owned per-CPU pointer for the memory type.
  • memstat_get_percpu_caller_uint64() โ€” Return a caller-owned per-CPU integer for the memory type.
  • memstat_set_percpu_caller_uint64() โ€” Set a caller-owned per-CPU integer for the memory type.
  • memstat_get_percpu_free() โ€” If the memory allocator supports a per-CPU cache, return the number of free items in the per-CPU cache of the designated CPU.

๐Ÿ’– RETURN VALUES ๐Ÿ’–

So like, libmemstat functions are basically sorted into three whole categories, and honestly? Each one vibes differently:

  • functions that return a pointer to an object
  • functions that return an integer return value
  • functions implementing accessor methods that hand you data out of a struct memory_type

๐ŸŽ€ The pointer-returning girlies

Functions that return a pointer to an object will generally return NULL on failure. But like, one specific case to know: memstat_mtl_alloc() will hand back an error value via errno, and that value will be ENOMEM. Noted. ๐Ÿ’…

And then there's memstat_mtl_first(), memstat_mtl_next(), and memstat_mtl_find() โ€” these return NULL when there's no entry or no match in the list. But bestie, listen: this is NOT a failure mode. It's just... empty. No error value is available for it, because nothing went wrong. It's giving "no thoughts, head empty," not "crisis." ๐Ÿฆ‹

โœจ The integer-returning girlies

Functions returning an integer success value will return 0 on success, or -1 on failure. Simple. Iconic.

If a failure gets returned, you can hit up the list error access method, memstat_mtl_geterror(), to retrieve the error state. And the string representation of that error? That's memstat_strerror()'s whole job. ๐Ÿซถ

Here are all the possible error values, no cap:

  • MEMSTAT_ERROR_UNDEFINED โ€” undefined error. This one happens if memstat_mtl_geterror() gets called on a list before any error associated with that list has occurred. Like asking "what's wrong" before anything's wrong. ๐Ÿ™ˆ
  • MEMSTAT_ERROR_NOMEMORY โ€” insufficient memory. Happens if library calls to malloc(3) fail, or if a system call to retrieve kernel statistics fails with ENOMEM. The budget is simply not budgeting. ๐Ÿ’€
  • MEMSTAT_ERROR_VERSION โ€” returned if the current version of libmemstat is unable to interpret the statistics data returned by the kernel, either due to an explicit version mismatch, or to differences in data structures that cannot be reconciled. It's a compatibility situationship. ๐Ÿ˜ญ
  • MEMSTAT_ERROR_PERMISSION โ€” returned if a statistics source returns errno values of EACCES or EPERM. You don't have the keys to this club, bestie. ๐Ÿ”’
  • MEMSTAT_ERROR_DATAERROR โ€” returned if libmemstat is unable to interpret statistics data returned by the data source, even though there does not appear to be a version problem. So the data is just... weird. Not deprecated, just unhinged.
  • MEMSTAT_ERROR_KVM โ€” returned if libmemstat experiences an error while using kvm(3) interfaces to query statistics data. Hit up kvm_geterr(3) to retrieve the error. ๐Ÿซก
  • MEMSTAT_ERROR_KVM_NOSYMBOL โ€” returned if libmemstat is unable to read a required symbol from the kernel being operated on. The symbol said "not today." ๐ŸŒธ
  • MEMSTAT_ERROR_KVM_SHORTREAD โ€” returned if libmemstat attempts to read data from a live memory image or kernel core dump and insufficient data is returned. Short read, shortened patience. โญ

๐Ÿ’œ The accessor girlies

And finally: functions that return data from a struct memory_type pointer are not permitted to fail. They just directly return either a statistic or a pointer to a string. No drama, no -1, they simply ate and left no crumbs. ๐Ÿ‘‘


๐ŸŒˆ EXAMPLES ๐ŸŒˆ

Okay so here's the whole plan: create a memory type list, query the uma(9) memory allocator for available statistics, and print out the number of allocations performed by the mbuf zone. Watch and learn, bestie:

struct memory_type_list *mtlp;
struct memory_type *mtp;
uint64_t mbuf_count;

mtlp = memstat_mtl_alloc();
if (mtlp == NULL)
    err(-1, "memstat_mtl_alloc");
if (memstat_sysctl_uma(mtlp, 0) < 0)
    err(-1, "memstat_sysctl_uma");
mtp = memstat_mtl_find(mtlp, ALLOCATOR_UMA, "mbuf");
if (mtp == NULL)
    errx(-1, "memstat_mtl_find: mbuf not found");
mbuf_count = memstat_get_count(mtp);
memstat_mtl_free(mtlp);

printf("mbufs: %llu\n", (unsigned long long)mbuf_count);

Like, the whole arc: allocate, query, find, count, free, print. Character development fr fr. ๐Ÿงš


๐Ÿ‘€ SEE ALSO ๐Ÿ‘€

malloc(9), uma(9)


๐ŸŽ€ HISTORY ๐ŸŽ€

The libmemstat library appeared in FreeBSD 6.0. A literal era. โœจ


๐Ÿซถ AUTHORS ๐Ÿซถ

The kernel memory allocator changes necessary to support a general purpose monitoring library, along with the library itself, were written by Robert Watson <rwatson@FreeBSD.org>. We stan a legend. ๐Ÿ‘‘


๐Ÿ’€ BUGS ๐Ÿ’€

Okay so there are some real ones, bestie, don't shoot the messenger:

  • There are memory allocators in the kernel โ€” such as the VM page allocator and the sf_buf allocator โ€” which are not currently supported by libmemstat. They're just not invited to the party yet. ๐Ÿ™ˆ
  • Once a memory type is present on a memory type list, it will not be removed even if the kernel no longer presents information on the type via its monitoring interfaces. So if you need to flush removed memory types, it is necessary to free the entire list and allocate a new one. Like, the whole list. No shortcuts, no individual evictions. Periodt. ๐Ÿ’…
Fr fr on that second one: stale entries stick around forever unless you nuke the list and rebuild it. Plan accordingly. ๐Ÿ”ฅ

And that's the whole page, bestie โ€” this one's from the Debian build, dated February 11, 2014, LIBMEMSTAT(3). Go forth and monitor some memory, you gorgeous thing. ๐Ÿฆ‹๐Ÿ’–โœจ

๐Ÿ“ฆ where this page comes from