Skip to content

Commit

Permalink
feat(metrics) histograms with user-defined bins
Browse files Browse the repository at this point in the history
This feature is only available through the FFI.

If a user provides a list of numbers when defining a histogram, those
numbers will be used as the upper-bounds of the histogram's bins.

For instance, the following code:

```
local shm = require "resty.wasmx.shm"
shm.metrics:define("h", shm.metrics.HISTOGRAM, { bins = { 1, 3, 5 } })
```

Creates a histogram with bins `[0, 1]`, `(1, 3]`, `(3, 5]` and
`(5, Inf+]`
  • Loading branch information
casimiro committed Oct 15, 2024
1 parent ea1475c commit bbfdd98
Show file tree
Hide file tree
Showing 14 changed files with 231 additions and 32 deletions.
12 changes: 9 additions & 3 deletions docs/METRICS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,26 @@ increased in some cases.

The above example demonstrates a histogram with ranges (or bins) whose
upper-bound grows in powers of 2, i.e. `2^0`, `2^1`, and `2^2`. This is usually
called "logarithmic binning" and is how histograms bins are represented in
ngx_wasm_module.
called "logarithmic binning" and is how histograms bins are by default
represented in ngx_wasm_module.

This binning strategy implies that when a value `v` is recorded, it is matched
with the smallest power of two that is bigger than `v`. This value is the
*upper-bound* of the bin associated with `v`. If the histogram contains or can
contain such a bin, that bin's counter is incremented. If not, the bin with the
next smallest upper-bound bigger than `v` has its counter incremented instead.

Histograms can also be created with a fixed set of bins. These histograms are
similar to the logarithmic-binning ones, except for their number of bins, and
respective upper-bounds, being user-defined instead of given by powers of 2, and
for their non-expandable nature.

[Back to TOC](#table-of-contents)

## Histogram Update and Expansion

Histograms are created with 5 bins: 1 initialized and 4 uninitialized.
Histograms without a user-defined set of bins are created with 5 bins: 1
initialized and 4 uninitialized.

The bin initialized upon histogram creation has upper-bound `2^32` and its
counter is incremented if it is the only bin whose upper-bound is bigger than
Expand Down
40 changes: 38 additions & 2 deletions lib/resty/wasmx/shm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ local tonumber = tonumber
local min = math.min
local new_tab = table.new
local insert = table.insert
local sort = table.sort
local str_fmt = string.format
local ffi_cast = ffi.cast
local ffi_fill = ffi.fill
Expand Down Expand Up @@ -58,6 +59,11 @@ ffi.cdef [[
NGX_WA_METRIC_HISTOGRAM,
} ngx_wa_metric_type_e;

typedef enum {
NGX_WA_HISTOGRAM_LOG2,
NGX_WA_HISTOGRAM_CUSTOM,
} ngx_wa_histogram_type_e;

typedef struct {
ngx_uint_t value;
ngx_msec_t last_update;
Expand All @@ -69,6 +75,7 @@ ffi.cdef [[
} ngx_wa_metrics_bin_t;

typedef struct {
ngx_wa_histogram_type_e h_type;
uint8_t n_bins;
uint64_t sum;
ngx_wa_metrics_bin_t bins[];
Expand Down Expand Up @@ -109,6 +116,8 @@ ffi.cdef [[

ngx_int_t ngx_wa_ffi_shm_metric_define(ngx_str_t *name,
ngx_wa_metric_type_e type,
uint32_t *bins,
uint16_t n_bins,
uint32_t *metric_id);
ngx_int_t ngx_wa_ffi_shm_metric_increment(uint32_t metric_id,
ngx_uint_t value);
Expand All @@ -124,11 +133,13 @@ ffi.cdef [[

ngx_int_t ngx_wa_ffi_shm_metrics_one_slot_size();
ngx_int_t ngx_wa_ffi_shm_metrics_histogram_max_size();
ngx_int_t ngx_wa_ffi_shm_metrics_histogram_max_bins();
]]


local WASM_SHM_KEY = {}
local DEFAULT_KEYS_PAGE_SIZE = 500
local HISTOGRAM_MAX_BINS = C.ngx_wa_ffi_shm_metrics_histogram_max_bins()


local _M = setmetatable({}, {
Expand Down Expand Up @@ -341,7 +352,7 @@ local function shm_kv_set(zone, key, value, cas)
end


local function metrics_define(zone, name, metric_type)
local function metrics_define(zone, name, metric_type, opts)
if type(name) ~= "string" or name == "" then
error("name must be a non-empty string", 2)
end
Expand All @@ -355,12 +366,37 @@ local function metrics_define(zone, name, metric_type)
error(err, 2)
end

local cbins = nil
local n_bins = 0

if opts ~= nil then
if type(opts) ~= "table" then
error("opts must be a table", 2)
end

if opts.bins ~= nil then
if type(opts.bins) ~= "table" then
error("opts.bins must be a table", 2)
end

if #opts.bins > HISTOGRAM_MAX_BINS then
local err = "opts.bins must have up to %d numbers"
error(str_fmt(err, HISTOGRAM_MAX_BINS), 2)
end

sort(opts.bins)

cbins = ffi_new("uint32_t[?]", #opts.bins, opts.bins)
n_bins = #opts.bins
end
end

name = "lua." .. name

local cname = ffi_new("ngx_str_t", { data = name, len = #name })
local m_id = ffi_new("uint32_t [1]")

local rc = C.ngx_wa_ffi_shm_metric_define(cname, metric_type, m_id)
local rc = C.ngx_wa_ffi_shm_metric_define(cname, metric_type, cbins, n_bins, m_id)
if rc == FFI_ERROR then
return nil, "no memory"
end
Expand Down
53 changes: 47 additions & 6 deletions src/common/debug/ngx_wasm_debug_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,19 @@
static ngx_int_t
ngx_wasm_debug_init(ngx_cycle_t *cycle)
{
static size_t long_metric_name_len = NGX_MAX_ERROR_STR;
uint32_t mid;
ngx_str_t metric_name;
u_char buf[long_metric_name_len];

static ngx_wasm_phase_t ngx_wasm_debug_phases[] = {
static size_t long_metric_name_len = NGX_MAX_ERROR_STR;
uint32_t mid;
ngx_str_t metric_name;
ngx_wa_metric_t *m;
ngx_wa_metrics_histogram_t *h, *h2;
uint32_t bins[NGX_WA_METRICS_BINS_MAX + 1];
u_char buf[long_metric_name_len];
u_char m_buf[NGX_WA_METRICS_ONE_SLOT_SIZE];
u_char h_buf[NGX_WA_METRICS_HISTOGRAM_MAX_SIZE];
u_char h2_buf[NGX_WA_METRICS_HISTOGRAM_MAX_SIZE];
u_char zeros[NGX_WA_METRICS_HISTOGRAM_MAX_SIZE];

static ngx_wasm_phase_t ngx_wasm_debug_phases[] = {
{ ngx_string("a_phase"), 0, 0, 0 },
{ ngx_null_string, 0, 0, 0 }
};
Expand Down Expand Up @@ -60,6 +67,7 @@ ngx_wasm_debug_init(ngx_cycle_t *cycle)
ngx_wa_metrics_define(ngx_wasmx_metrics(cycle),
&metric_name,
NGX_WA_METRIC_COUNTER,
NULL, 0,
&mid) == NGX_BUSY
);

Expand All @@ -68,6 +76,16 @@ ngx_wasm_debug_init(ngx_cycle_t *cycle)
ngx_wa_metrics_define(ngx_wasmx_metrics(cycle),
&metric_name,
100,
NULL, 0,
&mid) == NGX_ABORT
);

/* invalid number of histogram bins */
ngx_wa_assert(
ngx_wa_metrics_define(ngx_wasmx_metrics(cycle),
ngx_wa_metric_type_name(NGX_WA_METRIC_HISTOGRAM),
NGX_WA_METRIC_HISTOGRAM,
bins, NGX_WA_METRICS_BINS_MAX + 1,
&mid) == NGX_ABORT
);

Expand All @@ -77,6 +95,29 @@ ngx_wasm_debug_init(ngx_cycle_t *cycle)
"unknown", 8) == 0
);

/* unknown histogram type */
ngx_memzero(m_buf, sizeof(m_buf));
ngx_memzero(h_buf, sizeof(h_buf));
ngx_memzero(h2_buf, sizeof(h2_buf));
ngx_memzero(zeros, sizeof(zeros));

m = (ngx_wa_metric_t *) m_buf;
h = (ngx_wa_metrics_histogram_t *) h_buf;
h->n_bins = NGX_WA_METRICS_BINS_MAX;
h->h_type = 10;
h->sum = 1;
ngx_wa_metrics_histogram_set_buffer(m, h_buf, sizeof(h_buf));

h2 = (ngx_wa_metrics_histogram_t *) h2_buf;

ngx_wa_assert(
ngx_wa_metrics_histogram_record(ngx_wasmx_metrics(cycle),
m, 0, 0, 1) == NGX_ERROR
);

ngx_wa_metrics_histogram_get(ngx_wasmx_metrics(cycle), m, 1, h2);
ngx_wa_assert(ngx_memcmp(h2_buf, zeros, sizeof(zeros)) == 0);

return NGX_OK;
}

Expand Down
4 changes: 2 additions & 2 deletions src/common/lua/ngx_wasm_lua_ffi.c
Original file line number Diff line number Diff line change
Expand Up @@ -409,12 +409,12 @@ ngx_wa_ffi_shm_kv_set(ngx_wa_shm_t *shm, ngx_str_t *k, ngx_str_t *v,

ngx_int_t
ngx_wa_ffi_shm_metric_define(ngx_str_t *name, ngx_wa_metric_type_e type,
uint32_t *metric_id)
uint32_t *bins, uint16_t n_bins, uint32_t *metric_id)
{
ngx_int_t rc;
ngx_wa_metrics_t *metrics = ngx_wasmx_metrics((ngx_cycle_t *) ngx_cycle);

rc = ngx_wa_metrics_define(metrics, name, type, metric_id);
rc = ngx_wa_metrics_define(metrics, name, type, bins, n_bins, metric_id);
if (rc != NGX_OK) {
return rc;
}
Expand Down
10 changes: 9 additions & 1 deletion src/common/lua/ngx_wasm_lua_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ ngx_int_t ngx_wa_ffi_shm_kv_set(ngx_wa_shm_t *shm, ngx_str_t *k,
ngx_str_t *v, uint32_t cas, unsigned *written);

ngx_int_t ngx_wa_ffi_shm_metric_define(ngx_str_t *name,
ngx_wa_metric_type_e type, uint32_t *metric_id);
ngx_wa_metric_type_e type, uint32_t *bins, uint16_t n_bins,
uint32_t *metric_id);
ngx_int_t ngx_wa_ffi_shm_metric_increment(uint32_t metric_id, ngx_uint_t value);
ngx_int_t ngx_wa_ffi_shm_metric_record(uint32_t metric_id, ngx_uint_t value);
ngx_int_t ngx_wa_ffi_shm_metric_get(uint32_t metric_id, ngx_str_t *name,
Expand Down Expand Up @@ -93,4 +94,11 @@ ngx_wa_ffi_shm_metrics_histogram_max_size()
}


ngx_int_t
ngx_wa_ffi_shm_metrics_histogram_max_bins()
{
return NGX_WA_METRICS_BINS_MAX;
}


#endif /* _NGX_WASM_LUA_FFI_H_INCLUDED_ */
80 changes: 71 additions & 9 deletions src/common/metrics/ngx_wa_histogram.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,25 @@ histogram_grow(ngx_wa_metrics_t *metrics, ngx_wa_metrics_histogram_t *h,


static ngx_wa_metrics_bin_t *
histogram_bin(ngx_wa_metrics_t *metrics, ngx_wa_metrics_histogram_t *h,
histogram_custom_bin(ngx_wa_metrics_histogram_t *h, ngx_uint_t n)
{
uint16_t i = 0;
ngx_wa_metrics_bin_t *b = NULL;

for (i = 0; i < h->n_bins; i++) {
b = &h->bins[i];

if (b->upper_bound >= n) {
break;
}
}

return b;
}


static ngx_wa_metrics_bin_t *
histogram_log2_bin(ngx_wa_metrics_t *metrics, ngx_wa_metrics_histogram_t *h,
ngx_uint_t n, ngx_wa_metrics_histogram_t **out)
{
size_t i, j = 0;
Expand Down Expand Up @@ -160,13 +178,23 @@ histogram_log(ngx_wa_metrics_t *metrics, ngx_wa_metric_t *m, uint32_t mid)


ngx_int_t
ngx_wa_metrics_histogram_add_locked(ngx_wa_metrics_t *metrics,
ngx_wa_metric_t *m)
ngx_wa_metrics_histogram_add_locked(ngx_wa_metrics_t *metrics, uint32_t *bins,
uint16_t cn_bins, ngx_wa_metric_t *m)
{
size_t i;
static uint16_t n_bins = NGX_WA_METRICS_BINS_INIT;
uint16_t j = 0, n_bins = NGX_WA_METRICS_BINS_INIT;
ngx_wa_histogram_type_e h_type = NGX_WA_HISTOGRAM_LOG2;
ngx_wa_metrics_histogram_t **h;

if (bins) {
if (cn_bins > NGX_WA_METRICS_BINS_MAX) {
return NGX_ABORT;
}

n_bins = cn_bins + 1;
h_type = NGX_WA_HISTOGRAM_CUSTOM;
}

for (i = 0; i < metrics->workers; i++) {
h = &m->slots[i].histogram;
*h = ngx_slab_calloc_locked(metrics->shm->shpool,
Expand All @@ -177,7 +205,16 @@ ngx_wa_metrics_histogram_add_locked(ngx_wa_metrics_t *metrics,
}

(*h)->n_bins = n_bins;
(*h)->bins[0].upper_bound = NGX_MAX_UINT32_VALUE;
(*h)->h_type = h_type;

if (bins) {
/* user-defined set of bins */
for (/* void */; j < n_bins - 1; j++) {
(*h)->bins[j].upper_bound = bins[j];
}
}

(*h)->bins[j].upper_bound = NGX_MAX_UINT32_VALUE;
}

return NGX_OK;
Expand Down Expand Up @@ -205,8 +242,20 @@ ngx_wa_metrics_histogram_record(ngx_wa_metrics_t *metrics, ngx_wa_metric_t *m,
h = m->slots[slot].histogram;
h->sum += n;

b = histogram_bin(metrics, h, n, &m->slots[slot].histogram);
b->count += 1;
switch (h->h_type) {
case NGX_WA_HISTOGRAM_LOG2:
b = histogram_log2_bin(metrics, h, n, &m->slots[slot].histogram);
break;
case NGX_WA_HISTOGRAM_CUSTOM:
b = histogram_custom_bin(h, n);
break;
default:
return NGX_ERROR;
}

if (b) {
b->count += 1;
}

#if (NGX_DEBUG)
histogram_log(metrics, m, mid);
Expand All @@ -226,16 +275,29 @@ ngx_wa_metrics_histogram_get(ngx_wa_metrics_t *metrics, ngx_wa_metric_t *m,

for (i = 0; i < slots; i++) {
h = m->slots[i].histogram;
out->sum += h->sum;

for (j = 0; j < h->n_bins; j++) {
b = &h->bins[j];
out_b = histogram_bin(metrics, out, b->upper_bound, NULL);

switch (h->h_type) {
case NGX_WA_HISTOGRAM_LOG2:
out_b = histogram_log2_bin(metrics, out, b->upper_bound, NULL);
break;
case NGX_WA_HISTOGRAM_CUSTOM:
out_b = &out->bins[j];
out_b->upper_bound = b->upper_bound;
break;
default:
return;
}

out_b->count += b->count;

if (b->upper_bound == NGX_MAX_UINT32_VALUE) {
break;
}
}

out->sum += h->sum;
}
}
Loading

0 comments on commit bbfdd98

Please sign in to comment.