Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[merge] feat(metrics) histograms with user-defined bins #610

Merged
merged 1 commit into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 32 additions & 22 deletions docs/METRICS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ memory necessary for your use-case.

- [Types of Metrics](#types-of-metrics)
- [Name Prefixing](#name-prefixing)
- [Histogram Binning Strategy](#histogram-binning-strategy)
- [Histogram Binning Strategies](#histogram-binning-strategies)
- [Logarithmic Binning](#logarithmic-binning)
- [Custom Binning](#custom-binning)
- [Histogram Update and Expansion](#histogram-update-and-expansion)
- [Memory Consumption](#memory-consumption)
- [Shared Memory Allocation](#shared-memory-allocation)
Expand Down Expand Up @@ -46,35 +48,43 @@ increased in some cases.

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

## Histogram Binning Strategy
## Histogram Binning Strategies

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.
### Logarithmic Binning

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.
By default, histograms use a logarithmic-binning strategy. This is the only
available binning strategy when using the Proxy-Wasm SDK at this time.

[Back to TOC](#table-of-contents)
As an example of logarithmic-binning, take the histogram with ranges (i.e.
"bins") `[0, 1] (1, 2] (2, 4] (4, Inf]`: each bin's upper-bound is growing in
powers of 2: `2^0`, `2^1`, and `2^2`. In logarithmic-binning, a value `v` being
recorded 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, then its counter is incremented. If not, the
bin with the next smallest upper-bound bigger than `v` has its counter
incremented instead.

In ngx_wasm_module, logarithmic-binning histograms are created with one
initialized bin with upper-bound `2^32`. The counter for this bin is incremented
if it is the only bin whose upper-bound is bigger than the recorded value.

## Histogram Update and Expansion
When a value `v` is recorded and its bin does not yet exist, a new bin with the
upper-bound associated with `v` is initialized and its counter is incremented.

Histograms are created with 5 bins: 1 initialized and 4 uninitialized.
A logarithmic-binning histogram can contain up to 18 initialized bins.

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

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
the recorded value.
### Custom Binning

If a value `v` is recorded and its bin is not part of the initialized bins, a
new bin with the upper-bound associated with `v` is initialized, and its counter
is incremented.
Through the Lua FFI library provided with this module, histograms can also be
created with a fixed set of bins with user-defined upper-bounds. These
histograms store values exactly like the logarithmic-binning ones, except the
number of bins and their upper-bounds are user-defined and pre-initialized.

If the histogram is out of uninitialized bins, it can be expanded up to 18
bins so as to accommodate the additional bins for other ranges of `v`.
A custom-binning histogram can contain up to 18 bins (17 user-defined bins + one
`2^32` upper-bound bin). Custom-binning histograms cannot be expanded with new
bins after definition.

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

Expand Down
59 changes: 54 additions & 5 deletions lib/resty/wasmx/shm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,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 +74,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 +115,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 +132,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 @@ -182,7 +192,8 @@ local function key_iterator(ctx)
ctx.ccur_index[0] = 0

local rc = C.ngx_wa_ffi_shm_iterate_keys(ctx.shm, ctx.page_size,
ctx.clast_index, ctx.ccur_index, ctx.ckeys)
ctx.clast_index, ctx.ccur_index,
ctx.ckeys)

if rc == FFI_ABORT then
-- users must manage locking themselves (e.g. break condition in the for loop)
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 @@ -351,16 +362,54 @@ local function metrics_define(zone, name, metric_type)
" resty.wasmx.shm.metrics.COUNTER," ..
" resty.wasmx.shm.metrics.GAUGE, or" ..
" resty.wasmx.shm.metrics.HISTOGRAM"

error(err, 2)
end

local cbins
local n_bins = 0

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

if metric_type == _types.ffi_metric.HISTOGRAM
and 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 cannot have more than %d numbers"
error(str_fmt(err, HISTOGRAM_MAX_BINS - 1), 2)
end

local previous = 0

for _, n in ipairs(opts.bins) do
if type(n) ~= "number"
or n < 0 or n % 1 > 0 or n <= previous
then
error("opts.bins must be an ascending list of " ..
"positive integers", 2)
end

previous = n
end

n_bins = #opts.bins
cbins = ffi_new("uint32_t[?]", 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 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_HISTOGRAM_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_HISTOGRAM_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_HISTOGRAM_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_HISTOGRAM_BINS_MAX;
}


#endif /* _NGX_WASM_LUA_FFI_H_INCLUDED_ */
Loading
Loading