STATS(3)
okay bestie, buckle up β this is STATS(3), straight out of the Library Functions Manual, and it's the whole stats-gathering situation. π
πΈ NAME πΈ
stats β statistics gathering β¨
π LIBRARY π
library βlibstatsβ π
β¨ SYNOPSIS β¨
First up, the include stack β no skipping, bestie:
#include <sys/arb.h>
#include <sys/qmath.h>
#include <sys/stats.h>
π Stats Blob Template Management Functions π
int
stats_tpl_alloc(const char *name, uint32_t flags);
int
stats_tpl_fetch_allocid(const char *name, uint32_t hash);
int
stats_tpl_fetch(int tpl_id, struct statsblob_tpl **tpl);
int
stats_tpl_id2name(uint32_t tpl_id, char *buf, size_t len);
int
stats_tpl_sample_rates(SYSCTL_HANDLER_ARGS);
int
stats_tpl_sample_rollthedice(struct stats_tpl_sample_rate *rates,
int nrates, void *seed_bytes, size_t seed_len);
struct voistatspec
STATS_VSS_SUM();
struct voistatspec
STATS_VSS_MAX();
struct voistatspec
STATS_VSS_MIN();
struct voistatspec
STATS_VSS_CRHIST<32|64>_LIN(lb, ub, stepinc, vsdflags);
struct voistatspec
STATS_VSS_CRHIST<32|64>_EXP(lb, ub, stepbase, stepexp, vsdflags);
struct voistatspec
STATS_VSS_CRHIST<32|64>_LINEXP(lb, ub, nlinsteps, stepbase, vsdflags);
struct voistatspec
STATS_VSS_CRHIST<32|64>_USR(Sy,HBKTS,Ns,Pq,Sy,CRBKT,Ns,(,Em,lb,), ,,...,
,, vsdflags);
struct voistatspec
STATS_VSS_DRHIST<32|64>_USR(Sy,HBKTS,Ns,Pq,Sy,DRBKT,Ns,(,Em,lb, ,,ub,),
,,..., ,, vsdflags);
struct voistatspec
STATS_VSS_DVHIST<32|64>_USR(Sy,HBKTS,Ns,Pq,Sy,DVBKT,Ns,(,Em,val,),
,,..., ,, vsdflags);
struct voistatspec
STATS_VSS_TDGSTCLUST<32|64>(nctroids, prec);
int
stats_tpl_add_voistats(uint32_t tpl_id, int32_t voi_id,
const char *voi_name, enum vsd_dtype voi_dtype, uint32_t nvss,
struct voistatspec *vss, uint32_t flags);
π§ Stats Blob Data Gathering Functions π§
int
stats_voi_update_<abs|rel>_<dtype>(struct statsblob *sb, int32_t voi_id,
<dtype> voival);
π¦ Stats Blob Utility Functions π¦
struct statsblob *
stats_blob_alloc(uint32_t tpl_id, uint32_t flags);
int
stats_blob_init(struct statsblob *sb, uint32_t tpl_id, uint32_t flags);
int
stats_blob_clone(struct statsblob **dst, size_t dstmaxsz,
struct statsblob *src, uint32_t flags);
void
stats_blob_destroy(struct statsblob *sb);
int
stats_voistat_fetch_dptr(struct statsblob *sb, int32_t voi_id,
enum voi_stype stype, enum vsd_dtype *retdtype,
struct voistatdata **retvsd, size_t *retvsdsz);
int
stats_voistat_fetch_<dtype>(struct statsblob *sb, int32_t voi_id,
enum voi_stype stype, <dtype> *ret);
int
stats_blob_snapshot(struct statsblob **dst, size_t dstmaxsz,
struct statsblob *src, uint32_t flags);
int
stats_blob_tostr(struct statsblob *sb, struct sbuf *buf,
enum sb_str_fmt fmt, uint32_t flags);
int
stats_voistatdata_tostr(const struct voistatdata *vsd,
enum vsd_dtype dtype, enum sb_str_fmt fmt, struct sbuf *buf,
int objdump);
typedef int
(*stats_blob_visitcb_t)(struct sb_visit *sbv, void *usrctx);
int
stats_blob_visit(struct statsblob *sb, stats_blob_visitcb_t func,
void *usrctx);
π DESCRIPTION π
The stats framework is literally here to make real-time kernel and user space statistics gathering happen. β¨ The whole thing is built around the βstatsblobβ, an object embedded within a contiguous memory allocation that is mostly opaque to consumers and stores all required state. A βstatsblobβ object can itself be embedded within other objects either directly or indirectly using a pointer. π«Ά
Objects or subsystems for which statistics are to be gathered are initialized from a template βstatsblobβ, which acts as the blueprint for an arbitrary set of Variables Of Interest (VOIs) and their associated statistics. Each template defines a schema plus associated metadata, which are kept separate to minimize the memory footprint of blobs. π
Data gathering hook functions added at appropriate locations within the code base of interest feed VOI data into the framework for processing. π
Each βstatsblobβ, consists of a struct statsblob header and opaque internal blob structure per the following diagram:
---------------------------------------------------------
| struct | uint8_t |
| statsblob | opaque[] |
---------------------------------------------------------
The publicly visible 8-byte header is defined as:
struct statsblob {
uint8_t abi;
uint8_t endian;
uint16_t flags;
uint16_t maxsz;
uint16_t cursz;
uint8_t opaque[];
};
abi specifies which API version the blob's opaque internals conform to (STATS_ABI_V1 is the only version currently defined). endian specifies the endianness of the blob's fields (SB_LE for little endian, SB_BE for big endian, or SB_UE for unknown endianness). cursz specifies the size of the blob, while maxsz specifies the size of the underlying memory allocation in which the blob is embedded. Both cursz and maxsz default to units of bytes, unless a flag is set in flags that dictates otherwise. π
Templates are constructed by associating arbitrary VOI IDs with a set of statistics, where each statistic is specified using a struct voistatspec per the definition below:
struct voistatspec {
vss_hlpr_fn hlpr;
struct vss_hlpr_info *hlprinfo;
struct voistatdata *iv;
size_t vsdsz;
uint32_t flags;
enum vsd_dtype vs_dtype : 8;
enum voi_stype stype : 8;
};
It is generally expected that consumers will not work with struct voistatspec directly, and instead use the STATS_VSS_*() helper macros. π§
The stats framework offers the following statistics for association with VOIs:
VS_STYPE_SUMβ The sum of VOI values. β¨VS_STYPE_MAXβ The maximum VOI value. π₯VS_STYPE_MINβ The minimum VOI value. πΈVS_STYPE_HISTβ A static bucket histogram of VOI values, including a count of βout-of-band/bucketβ values which did not match any bucket. Histograms can be specified as βContinuous Rangeβ (CRHIST), βDiscrete Rangeβ (DRHIST) or βDiscrete Valueβ (DVHIST), with 32 or 64 bit bucket counters, depending on the VOI semantics. πVS_STYPE_TDGSTβ A dynamic bucket histogram of VOI values based on the t-digest method (refer to the t-digest paper in the βSEE ALSOβ section below). π
A βvisitor software design patternβ-like scheme is employed to facilitate iterating over a blob's data without concern for the blob's structure. The data provided to visitor callback functions is encapsulated in struct sb_visit per the definition below:
struct sb_visit {
struct voistatdata *vs_data;
uint32_t tplhash;
uint32_t flags;
int16_t voi_id;
int16_t vs_dsz;
enum vsd_dtype voi_dtype : 8;
enum vsd_dtype vs_dtype : 8;
int8_t vs_stype;
uint16_t vs_errs;
};
The stats_tpl_sample_rates() and stats_tpl_sample_rollthedice() functions utilize struct stats_tpl_sample_rate to encapsulate per-template sample rate information per the definition below:
struct stats_tpl_sample_rate {
int32_t tpl_slot_id;
uint32_t tpl_sample_pct;
};
The tpl_slot_id member holds the template's slot ID obtained from stats_tpl_alloc() or stats_tpl_fetch_allocid(). The tpl_sample_pct member holds the template's sample rate as an integer percentage in the range [0,100]. π―
The stats_tpl_sr_cb_t conformant function pointer that is required as the arg1 of stats_tpl_sample_rates() is defined as:
enum stats_tpl_sr_cb_action {
TPL_SR_UNLOCKED_GET,
TPL_SR_RLOCKED_GET,
TPL_SR_RUNLOCK,
TPL_SR_PUT
};
typedef int (*stats_tpl_sr_cb_t)(enum stats_tpl_sr_cb_action action,
struct stats_tpl_sample_rate **rates, int *nrates, void *ctx);
It is required that a conformant function: π«Ά
- Return an appropriate
errno(2)on error, otherwise0. - When called with
"action == TPL_SR_*_GET", return the subsystem's rates list ptr and count, locked or unlocked as requested. - When called with
"action == TPL_SR_RUNLOCK", unlock the subsystem's rates list ptr and count. Pair with a prior"action == TPL_SR_RLOCKED_GET"call. - When called with
"action == TPL_SR_PUT", update the subsystem's rates list ptr and count to the sysctl processed values and return the inactive list details inratesandnratesfor garbage collection bystats_tpl_sample_rates().
Where templates need to be referenced via textual means, for example via a MIB variable, the following string based template spec formats can be used:
"<tplname>":<tplhash>, for example"TCP_DEFAULT":1731235399"<tplname>", for example"TCP_DEFAULT":<tplhash>, for example:1731235399
The first form is the normative spec format generated by the framework, while the second and third forms are convenience formats primarily for user input. The use of inverted commas around the template name is optional. π
π MIB Variables π
The in-kernel stats framework exposes the following framework-specific variables in the kern.stats branch of the sysctl(3) MIB.
templatesβ Read-only CSV list of registered templates in normative template spec form. π
π Template Management Functions π
stats_tpl_alloc()β allocates a new template with the specified unique name and returns its runtime-stable template slot ID for use with other API functions. Theflagsargument is currently unused. β¨stats_tpl_fetch_allocid()β returns the runtime-stable template slot ID of any registered template matching the specifiednameandhash. πstats_tpl_fetch()β returns the pointer to the registered template object at the specified template slot ID. π―stats_tpl_id2name()β returns the name of the registered template object at the specified template slot ID. π
okay so bestie, stats_tpl_sample_rates() is literally the generic handler for template sample rates management and reporting via sysctl(3) MIB variables π
. Subsystems can use it to spin up a subsystem-specific SYSCTL_PROC(9) MIB variable that manages and reports subsystem-specific template sampling rates. A couple of ground rules, no cap:
- Subsystems must supply a
stats_tpl_sr_cb_tconformant function pointer as the sysctl'sarg1β that's the callback used to interact with the subsystem's stats template sample rates list π«Ά - Subsystems can optionally specify the sysctl's
arg2as non-zero, and that causes a zero-initialized allocation ofarg2-sized contextual memory to be heap-allocated and passed in to all subsystem callbacks made during the operation ofstats_tpl_sample_rates()β¨
Then we've got stats_tpl_sample_rollthedice() β yes that's the real name, we love her π²π. She makes a weighted random template selection from the supplied array of template sampling rates. Heads up: the cumulative percentage of all sampling rates should not exceed 100 π.
- If no seed is supplied, a PRNG is used to generate a true random number so that every selection is independent π₯
- If a seed is supplied, selection will be made randomly across different seeds, but deterministically given the same seed π§
stats_tpl_add_voistats() is the one that adds a VOI and its associated set of statistics to the registered template object at the specified template slot ID πΈ. The set of statistics gets passed as an array of struct voistatspec, which you can initialize using the STATS_VSS_*() helper macros or manually for non-standard use cases π.
- For static
vssarrays, thenvsscount of array elements can be determined by passingvssto theNVSS()macro β¨ - The
SB_VOI_RELUPDATEflag can be passed to configure the VOI for use withstats_voi_update_rel_<dtype>(), which entails maintaining an extra 8 bytes of state in the blob at each update π
Data Gathering Functions
The stats_voi_update_abs_<dtype>() and stats_voi_update_rel_<dtype>() functions both update all the statistics associated with the VOI identified by voi_id π«Ά. The difference is literally just the vibe of voival:
- The βabsβ call uses
voivalas an absolute value π - The βrelβ call uses
voivalas a value relative to that of the previous update function call, by adding it to the previous value and using the result for the update β¨
Okay so this is the important bit: relative updates are only possible for VOIs that were added to the template with theSB_VOI_RELUPDATEflag specified tostats_tpl_add_voistats(). No flag, no rel updates, periodt π
Utility Functions
stats_blob_alloc() allocates and initializes a new blob based on the registered template object at the specified template slot ID π.
stats_blob_init() initializes a new blob in an existing memory allocation based on the registered template object at the specified template slot ID πΈ.
stats_blob_clone() duplicates the src blob into dst, leaving only the maxsz field of dst untouched π. The flags, bestie:
SB_CLONE_ALLOCDSTβ pass this to instruct the function to allocate a new blob of appropriate size into which to clonesrc, storing the new pointer in*dstβ¨SB_CLONE_USRDSTNOFAULTβ set this to signal thatcopyout_nofault(9)should be used, because*dstis a user space address π₯SB_CLONE_USRDSTβ set this to signal thatcopyout(9)should be used, for the same reason π«Ά
stats_blob_snapshot() calls stats_blob_clone() to obtain a copy of src and then performs any additional functions required to produce a coherent blob snapshot π. The flags interpreted by stats_blob_clone() also apply to stats_blob_snapshot(). Additionally, the SB_CLONE_RSTSRC flag can be used to effect a reset of the src blob's statistics after a snapshot is successfully taken π.
stats_blob_destroy() destroys a blob previously created with stats_blob_alloc(), stats_blob_clone() or stats_blob_snapshot() π
.
stats_blob_visit() lets the caller iterate over the contents of a blob π¦. The callback function func is called for every VOI and statistic in the blob, passing a struct sb_visit and the user context argument usrctx to the callback function. The sbv passed to the callback function may have one or more of the following flags set in the flags struct member to provide useful metadata about the iteration:
SB_IT_FIRST_CBSB_IT_LAST_CBSB_IT_FIRST_VOISB_IT_LAST_VOISB_IT_FIRST_VOISTATSB_IT_LAST_VOISTATSB_IT_NULLVOISB_IT_NULLVOISTAT
Returning a non-zero value from the callback function terminates the iteration π.
stats_blob_tostr() renders a string representation of a blob into the sbuf(9) buf β¨. Currently supported render formats are SB_STRFMT_FREEFORM and SB_STRFMT_JSON. The flags:
SB_TOSTR_OBJDUMPβ pass to render version specific opaque implementation detail for debugging or string-to-binary blob reconstruction purposes πSB_TOSTR_METAβ pass to render template metadata into the string representation, using the blob's template hash to lookup the corresponding template π
stats_voistatdata_tostr() renders a string representation of an individual statistic's data into the sbuf(9) buf πΈ. The same render formats supported by the stats_blob_tostr() function can be specified, and the objdump boolean has the same meaning as the SB_TOSTR_OBJDUMP flag π.
stats_voistat_fetch_dptr() returns an internal blob pointer to the specified stype statistic data for the VOI voi_id π₯. And the stats_voistat_fetch_<dtype>() functions are convenience wrappers around stats_voistat_fetch_dptr() to perform the extraction for simple data types π§.
π IMPLEMENTATION NOTES π
The following notes apply to STATS_ABI_V1 format statsblobs π.
π Space-Time Complexity π
Blobs are laid out as three distinct memory regions following the header, and here's the little diagram bestie:
------------------------------------------------------
| struct | struct | struct | struct |
| statsblobv1 | voi [] | voistat [] | voistatdata [] |
------------------------------------------------------
Blobs store VOI and statistic blob state (8 bytes for struct voi and 8 bytes for struct voistat respectively) in sparse arrays, using the voi_id and enum voi_stype as array indices π. This allows O(1) access to any voi/voistat pair in the blob, at the expense of 8 bytes of wasted memory per vacant slot for templates which do not specify contiguously numbered VOIs and/or statistic types. Data storage for statistics is only allocated for non-vacant slot pairs β¨.
To provide a concrete example, a blob with the following specification:
- Two VOIs; ID 0 and 2; added to the template in that order
- VOI 0 is of data type
int64_t, is configured withSB_VOI_RELUPDATEto enable support for relative updates usingstats_voi_update_rel_<dtype>(), and has aVS_STYPE_MINstatistic associated with it - VOI 2 is of data type
uint32_twithVS_STYPE_SUMandVS_STYPE_MAXstatistics associated with it.
would have the following memory layout:
--------------------------------------
| header | struct statsblobv1, 32 bytes
|------------------------------------|
| voi[0] | struct voi, 8 bytes
| voi[1] (vacant) | struct voi, 8 bytes
| voi[2] | struct voi, 8 bytes
|------------------------------------|
| voi[2]voistat[VOISTATE] (vacant) | struct voistat, 8 bytes
| voi[2]voistat[SUM] | struct voistat, 8 bytes
| voi[2]voistat[MAX] | struct voistat, 8 bytes
| voi[0]voistat[VOISTATE] | struct voistat, 8 bytes
| voi[0]voistat[SUM] (vacant) | struct voistat, 8 bytes
| voi[0]voistat[MAX] (vacant) | struct voistat, 8 bytes
| voi[0]voistat[MIN] | struct voistat, 8 bytes
|------------------------------------|
| voi[2]voistat[SUM]voistatdata | struct voistatdata_int32, 4 bytes
| voi[2]voistat[MAX]voistatdata | struct voistatdata_int32, 4 bytes
| voi[0]voistat[VOISTATE]voistatdata | struct voistatdata_numeric, 8 bytes
| voi[0]voistat[MIN]voistatdata | struct voistatdata_int64, 8 bytes
--------------------------------------
TOTAL 136 bytes
When rendered to string format using stats_blob_tostr(), the SB_STRFMT_FREEFORM fmt and the SB_TOSTR_OBJDUMP flag, the rendered output is:
struct statsblobv1@0x8016250a0, abi=1, endian=1, maxsz=136, cursz=136, \
created=6294158585626144, lastrst=6294158585626144, flags=0x0000, \
stats_off=56, statsdata_off=112, tplhash=2994056564
vois[0]: id=0, name="", flags=0x0001, dtype=INT_S64, voistatmaxid=3, \
stats_off=80
vois[0]stat[0]: stype=VOISTATE, flags=0x0000, dtype=VOISTATE, \
dsz=8, data_off=120
voistatdata: prev=0
vois[0]stat[1]: stype=-1
vois[0]stat[2]: stype=-1
vois[0]stat[3]: stype=MIN, flags=0x0000, dtype=INT_S64, \
dsz=8, data_off=128
voistatdata: 9223372036854775807
vois[1]: id=-1
vois[2]: id=2, name="", flags=0x0000, dtype=INT_U32, voistatmaxid=2, \
stats_off=56
vois[2]stat[0]: stype=-1
vois[2]stat[1]: stype=SUM, flags=0x0000, dtype=INT_U32, dsz=4, \
data_off=112
voistatdata: 0
vois[2]stat[2]: stype=MAX, flags=0x0000, dtype=INT_U32, dsz=4, \
data_off=116
voistatdata: 0
Note: The \ present in the rendered output above indicates a manual line break inserted to keep the man page within 80 columns and is not part of the actual output π (do not come for me, it's a formatting thing bestie).
π Locking π
The stats framework does not provide any concurrency protection at the individual blob level, instead requiring that consumers guarantee mutual exclusion when calling API functions that reference a non-template blob π. Like, that's on you, okay?
The list of templates is protected with a rwlock(9) in-kernel, and pthread(3) rw lock in user space to support concurrency between template management and blob initialization operations β¨π«Ά.
π RETURN VALUES π
okay bestie, this is the part where every function tells you how it went β like a read receipt but for your syscalls. pay attention, because the negative-errno girlies are a whole vibe π
stats_tpl_alloc()β slays and returns a runtime-stable template slot ID on success π on failure you get a negative errno, no cap.-EINVALshows up if any problems are detected with the arguments.-EEXISTmeans an existing template is already registered with the same name (name thief detected π¨).-ENOMEMmeans a required memory allocation failed β the malloc said "not today" π.stats_tpl_fetch_allocid()β returns a runtime-stable template slot ID, or negative errno on failure.-ESRCHis what you get if no registered template matches the specified name and/or hash β literally nothing found, ghosted fr fr π».stats_tpl_fetch()β returns 0 on success π«Ά orENOENTif an invalid tpl_id is specified.stats_tpl_id2name()β returns 0 on success, or an errno on failure.EOVERFLOWhappens if the length of buf specified by len is too short to hold the template's name (your buffer is giving tiny, give it room to breathe π).ENOENTis returned if an invalid tpl_id is specified.stats_tpl_sample_rollthedice()β returns a valid template slot id selected from rates, or-1if aNULLselection was made, that is no stats collection this roll π²β¨. the dice said no, and honestly? respect.stats_tpl_add_voistats()β returns 0 on success, or an errno on failure.EINVALis returned if any problems are detected with the arguments.EFBIGis returned if the resulting blob would have exceeded the maximum size (it's giving too much, and the blob cannot hold it π).EOPNOTSUPPis returned if an attempt is made to add more VOI stats to a previously configured VOI β that VOI is closed for business, bestie.ENOMEMis returned if a required memory allocation fails.stats_voi_update_abs_<dtype>()andstats_voi_update_rel_<dtype>()β return 0 on success, orEINVALif any problems are detected with the arguments.stats_blob_init()β returns 0 on success, or an errno on failure.EINVALis returned if any problems are detected with the arguments.EOVERFLOWis returned if the template blob's cursz is larger than the maxsz of the blob being initialized β the template is bigger than the fit, iconic but illegal π«.stats_blob_alloc()β returns a pointer to a newly allocated and initialized blob based on the specified template with slot ID tpl_id, orNULLif the memory allocation failed. fresh blob just dropped, no thoughts, head empty π¦.stats_blob_clone()andstats_blob_snapshot()β return 0 on success, or an errno on failure.EINVALis returned if any problems are detected with the arguments.ENOMEMis returned if theSB_CLONE_ALLOCDSTflag was specified and the memory allocation for dst fails.EOVERFLOWis returned if the src blob's cursz is larger than the maxsz of the dst blob β the source ate too much and the destination can't match its energy tbh.stats_blob_visit()β returns 0 on success, orEINVALif any problems are detected with the arguments.stats_blob_tostr()andstats_voistatdata_tostr()β return 0 on success, or an errno on failure.EINVALis returned if any problems are detected with the arguments, otherwise any error returned bysbuf_error()for buf is returned β you inherit whatever energy your buf was serving, iykyk.stats_voistat_fetch_dptr()β returns 0 on success, orEINVALif any problems are detected with the arguments.stats_voistat_fetch_<dtype>()β returns 0 on success, or an errno on failure.EINVALis returned if any problems are detected with the arguments.EFTYPEis returned if the requested data type does not match the blob's data type for the specified voi_id and stype β type mismatch, the vibes are simply not compatible π€.
π SEE ALSO π
the required reading, the syllabus, the linktree of this page π
also the absolute academic royalty:
Ted Dunning and Otmar Ertl, Computing Extremely Accurate Quantiles Using t-digests, https://github.com/tdunning/t-digest/raw/master/docs/t-digest-paper/histo.pdf
like they understood the assignment π π
π°οΈ HISTORY π°οΈ
The stats framework first appeared in FreeBSD 13.0 β so it's a gen-z-adjacent icon too, we love a fresh debut β¨
π AUTHORS π
The stats framework and this manual page were written by Lawrence Stewart <lstewart@FreeBSD.org> and sponsored by Netflix, Inc. β main character energy, fully produced and distributed ππ¬
β οΈ CAVEATS β οΈ
okay so this is the fine print and it's actually important, bestie:
Granularity of timing-dependent network statistics, in particular TCP_RTT, depends on the HZ timer. To minimize the measurement error avoid using HZ lower than 1000.
so like... if you crankHZdown, yourTCP_RTTnumbers get blurry. keepHZat1000or above or your measurements are gonna look like they were taken through a fogged-up mirror ππ
pink, pretty, and running on Debian, dated December 2, 2019 β that's the release info on this bad boy, and the version stamp is doing its little moment too π
π thank you for coming to my ted talk, go read some stats, periodt π«Άβ¨
π¦ where this page comes from
- freebsd 14.5: base 14.5
- freebsd 15.1: base 15.1
- Copyright (c) 2016-2018 Netflix, Inc
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms