Skip to content

Commit 3bb2f15

Browse files
casimirothibaultcha
authored andcommitted
feat(metrics) implement user-defined histogram bins
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] (5, Inf+]`. Signed-off-by: Thibault Charbonnier <[email protected]>
1 parent f516ba6 commit 3bb2f15

17 files changed

+355
-64
lines changed

docs/METRICS.md

+32-22
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ memory necessary for your use-case.
88

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

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

49-
## Histogram Binning Strategy
51+
## Histogram Binning Strategies
5052

51-
The above example demonstrates a histogram with ranges (or bins) whose
52-
upper-bound grows in powers of 2, i.e. `2^0`, `2^1`, and `2^2`. This is usually
53-
called "logarithmic binning" and is how histograms bins are represented in
54-
ngx_wasm_module.
53+
### Logarithmic Binning
5554

56-
This binning strategy implies that when a value `v` is recorded, it is matched
57-
with the smallest power of two that is bigger than `v`. This value is the
58-
*upper-bound* of the bin associated with `v`. If the histogram contains or can
59-
contain such a bin, that bin's counter is incremented. If not, the bin with the
60-
next smallest upper-bound bigger than `v` has its counter incremented instead.
55+
By default, histograms use a logarithmic-binning strategy. This is the only
56+
available binning strategy when using the Proxy-Wasm SDK at this time.
6157

62-
[Back to TOC](#table-of-contents)
58+
As an example of logarithmic-binning, take the histogram with ranges (i.e.
59+
"bins") `[0, 1] (1, 2] (2, 4] (4, Inf]`: each bin's upper-bound is growing in
60+
powers of 2: `2^0`, `2^1`, and `2^2`. In logarithmic-binning, a value `v` being
61+
recorded is matched with the smallest power of two that is bigger than `v`. This
62+
value is the *upper-bound* of the bin associated with `v`. If the histogram
63+
contains or can contain such a bin, then its counter is incremented. If not, the
64+
bin with the next smallest upper-bound bigger than `v` has its counter
65+
incremented instead.
66+
67+
In ngx_wasm_module, logarithmic-binning histograms are created with one
68+
initialized bin with upper-bound `2^32`. The counter for this bin is incremented
69+
if it is the only bin whose upper-bound is bigger than the recorded value.
6370

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

66-
Histograms are created with 5 bins: 1 initialized and 4 uninitialized.
74+
A logarithmic-binning histogram can contain up to 18 initialized bins.
75+
76+
[Back to TOC](#table-of-contents)
6777

68-
The bin initialized upon histogram creation has upper-bound `2^32` and its
69-
counter is incremented if it is the only bin whose upper-bound is bigger than
70-
the recorded value.
78+
### Custom Binning
7179

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

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

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

lib/resty/wasmx/shm.lua

+54-5
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ ffi.cdef [[
5858
NGX_WA_METRIC_HISTOGRAM,
5959
} ngx_wa_metric_type_e;
6060

61+
typedef enum {
62+
NGX_WA_HISTOGRAM_LOG2,
63+
NGX_WA_HISTOGRAM_CUSTOM,
64+
} ngx_wa_histogram_type_e;
65+
6166
typedef struct {
6267
ngx_uint_t value;
6368
ngx_msec_t last_update;
@@ -69,6 +74,7 @@ ffi.cdef [[
6974
} ngx_wa_metrics_bin_t;
7075

7176
typedef struct {
77+
ngx_wa_histogram_type_e h_type;
7278
uint8_t n_bins;
7379
uint64_t sum;
7480
ngx_wa_metrics_bin_t bins[];
@@ -109,6 +115,8 @@ ffi.cdef [[
109115

110116
ngx_int_t ngx_wa_ffi_shm_metric_define(ngx_str_t *name,
111117
ngx_wa_metric_type_e type,
118+
uint32_t *bins,
119+
uint16_t n_bins,
112120
uint32_t *metric_id);
113121
ngx_int_t ngx_wa_ffi_shm_metric_increment(uint32_t metric_id,
114122
ngx_uint_t value);
@@ -124,11 +132,13 @@ ffi.cdef [[
124132

125133
ngx_int_t ngx_wa_ffi_shm_metrics_one_slot_size();
126134
ngx_int_t ngx_wa_ffi_shm_metrics_histogram_max_size();
135+
ngx_int_t ngx_wa_ffi_shm_metrics_histogram_max_bins();
127136
]]
128137

129138

130139
local WASM_SHM_KEY = {}
131140
local DEFAULT_KEYS_PAGE_SIZE = 500
141+
local HISTOGRAM_MAX_BINS = C.ngx_wa_ffi_shm_metrics_histogram_max_bins()
132142

133143

134144
local _M = setmetatable({}, {
@@ -182,7 +192,8 @@ local function key_iterator(ctx)
182192
ctx.ccur_index[0] = 0
183193

184194
local rc = C.ngx_wa_ffi_shm_iterate_keys(ctx.shm, ctx.page_size,
185-
ctx.clast_index, ctx.ccur_index, ctx.ckeys)
195+
ctx.clast_index, ctx.ccur_index,
196+
ctx.ckeys)
186197

187198
if rc == FFI_ABORT then
188199
-- users must manage locking themselves (e.g. break condition in the for loop)
@@ -341,7 +352,7 @@ local function shm_kv_set(zone, key, value, cas)
341352
end
342353

343354

344-
local function metrics_define(zone, name, metric_type)
355+
local function metrics_define(zone, name, metric_type, opts)
345356
if type(name) ~= "string" or name == "" then
346357
error("name must be a non-empty string", 2)
347358
end
@@ -351,16 +362,54 @@ local function metrics_define(zone, name, metric_type)
351362
" resty.wasmx.shm.metrics.COUNTER," ..
352363
" resty.wasmx.shm.metrics.GAUGE, or" ..
353364
" resty.wasmx.shm.metrics.HISTOGRAM"
354-
355365
error(err, 2)
356366
end
357367

368+
local cbins
369+
local n_bins = 0
370+
371+
if opts ~= nil then
372+
if type(opts) ~= "table" then
373+
error("opts must be a table", 2)
374+
end
375+
376+
if metric_type == _types.ffi_metric.HISTOGRAM
377+
and opts.bins ~= nil
378+
then
379+
if type(opts.bins) ~= "table" then
380+
error("opts.bins must be a table", 2)
381+
end
382+
383+
if #opts.bins >= HISTOGRAM_MAX_BINS then
384+
local err = "opts.bins cannot have more than %d numbers"
385+
error(str_fmt(err, HISTOGRAM_MAX_BINS - 1), 2)
386+
end
387+
388+
local previous = 0
389+
390+
for _, n in ipairs(opts.bins) do
391+
if type(n) ~= "number"
392+
or n < 0 or n % 1 > 0 or n <= previous
393+
then
394+
error("opts.bins must be an ascending list of " ..
395+
"positive integers", 2)
396+
end
397+
398+
previous = n
399+
end
400+
401+
n_bins = #opts.bins
402+
cbins = ffi_new("uint32_t[?]", n_bins, opts.bins)
403+
end
404+
end
405+
358406
name = "lua." .. name
359407

360408
local cname = ffi_new("ngx_str_t", { data = name, len = #name })
361-
local m_id = ffi_new("uint32_t [1]")
409+
local m_id = ffi_new("uint32_t[1]")
362410

363-
local rc = C.ngx_wa_ffi_shm_metric_define(cname, metric_type, m_id)
411+
local rc = C.ngx_wa_ffi_shm_metric_define(cname, metric_type,
412+
cbins, n_bins, m_id)
364413
if rc == FFI_ERROR then
365414
return nil, "no memory"
366415
end

src/common/debug/ngx_wasm_debug_module.c

+47-6
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,19 @@
2222
static ngx_int_t
2323
ngx_wasm_debug_init(ngx_cycle_t *cycle)
2424
{
25-
static size_t long_metric_name_len = NGX_MAX_ERROR_STR;
26-
uint32_t mid;
27-
ngx_str_t metric_name;
28-
u_char buf[long_metric_name_len];
29-
30-
static ngx_wasm_phase_t ngx_wasm_debug_phases[] = {
25+
static size_t long_metric_name_len = NGX_MAX_ERROR_STR;
26+
uint32_t mid;
27+
ngx_str_t metric_name;
28+
ngx_wa_metric_t *m;
29+
ngx_wa_metrics_histogram_t *h, *h2;
30+
uint32_t bins[NGX_WA_METRICS_HISTOGRAM_BINS_MAX + 1];
31+
u_char buf[long_metric_name_len];
32+
u_char m_buf[NGX_WA_METRICS_ONE_SLOT_SIZE];
33+
u_char h_buf[NGX_WA_METRICS_HISTOGRAM_MAX_SIZE];
34+
u_char h2_buf[NGX_WA_METRICS_HISTOGRAM_MAX_SIZE];
35+
u_char zeros[NGX_WA_METRICS_HISTOGRAM_MAX_SIZE];
36+
37+
static ngx_wasm_phase_t ngx_wasm_debug_phases[] = {
3138
{ ngx_string("a_phase"), 0, 0, 0 },
3239
{ ngx_null_string, 0, 0, 0 }
3340
};
@@ -60,6 +67,7 @@ ngx_wasm_debug_init(ngx_cycle_t *cycle)
6067
ngx_wa_metrics_define(ngx_wasmx_metrics(cycle),
6168
&metric_name,
6269
NGX_WA_METRIC_COUNTER,
70+
NULL, 0,
6371
&mid) == NGX_BUSY
6472
);
6573

@@ -68,6 +76,16 @@ ngx_wasm_debug_init(ngx_cycle_t *cycle)
6876
ngx_wa_metrics_define(ngx_wasmx_metrics(cycle),
6977
&metric_name,
7078
100,
79+
NULL, 0,
80+
&mid) == NGX_ABORT
81+
);
82+
83+
/* invalid number of histogram bins */
84+
ngx_wa_assert(
85+
ngx_wa_metrics_define(ngx_wasmx_metrics(cycle),
86+
ngx_wa_metric_type_name(NGX_WA_METRIC_HISTOGRAM),
87+
NGX_WA_METRIC_HISTOGRAM,
88+
bins, NGX_WA_METRICS_HISTOGRAM_BINS_MAX + 1,
7189
&mid) == NGX_ABORT
7290
);
7391

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

98+
/* unknown histogram type */
99+
ngx_memzero(m_buf, sizeof(m_buf));
100+
ngx_memzero(h_buf, sizeof(h_buf));
101+
ngx_memzero(h2_buf, sizeof(h2_buf));
102+
ngx_memzero(zeros, sizeof(zeros));
103+
104+
m = (ngx_wa_metric_t *) m_buf;
105+
h = (ngx_wa_metrics_histogram_t *) h_buf;
106+
h->n_bins = NGX_WA_METRICS_HISTOGRAM_BINS_MAX;
107+
h->h_type = 10;
108+
h->sum = 1;
109+
ngx_wa_metrics_histogram_set_buffer(m, h_buf, sizeof(h_buf));
110+
111+
h2 = (ngx_wa_metrics_histogram_t *) h2_buf;
112+
113+
ngx_wa_assert(
114+
ngx_wa_metrics_histogram_record(ngx_wasmx_metrics(cycle),
115+
m, 0, 0, 1) == NGX_ERROR
116+
);
117+
118+
ngx_wa_metrics_histogram_get(ngx_wasmx_metrics(cycle), m, 1, h2);
119+
ngx_wa_assert(ngx_memcmp(h2_buf, zeros, sizeof(zeros)) == 0);
120+
80121
return NGX_OK;
81122
}
82123

src/common/lua/ngx_wasm_lua_ffi.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -409,12 +409,12 @@ ngx_wa_ffi_shm_kv_set(ngx_wa_shm_t *shm, ngx_str_t *k, ngx_str_t *v,
409409

410410
ngx_int_t
411411
ngx_wa_ffi_shm_metric_define(ngx_str_t *name, ngx_wa_metric_type_e type,
412-
uint32_t *metric_id)
412+
uint32_t *bins, uint16_t n_bins, uint32_t *metric_id)
413413
{
414414
ngx_int_t rc;
415415
ngx_wa_metrics_t *metrics = ngx_wasmx_metrics((ngx_cycle_t *) ngx_cycle);
416416

417-
rc = ngx_wa_metrics_define(metrics, name, type, metric_id);
417+
rc = ngx_wa_metrics_define(metrics, name, type, bins, n_bins, metric_id);
418418
if (rc != NGX_OK) {
419419
return rc;
420420
}

src/common/lua/ngx_wasm_lua_ffi.h

+9-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ ngx_int_t ngx_wa_ffi_shm_kv_set(ngx_wa_shm_t *shm, ngx_str_t *k,
5858
ngx_str_t *v, uint32_t cas, unsigned *written);
5959

6060
ngx_int_t ngx_wa_ffi_shm_metric_define(ngx_str_t *name,
61-
ngx_wa_metric_type_e type, uint32_t *metric_id);
61+
ngx_wa_metric_type_e type, uint32_t *bins, uint16_t n_bins,
62+
uint32_t *metric_id);
6263
ngx_int_t ngx_wa_ffi_shm_metric_increment(uint32_t metric_id, ngx_uint_t value);
6364
ngx_int_t ngx_wa_ffi_shm_metric_record(uint32_t metric_id, ngx_uint_t value);
6465
ngx_int_t ngx_wa_ffi_shm_metric_get(uint32_t metric_id, ngx_str_t *name,
@@ -93,4 +94,11 @@ ngx_wa_ffi_shm_metrics_histogram_max_size()
9394
}
9495

9596

97+
ngx_int_t
98+
ngx_wa_ffi_shm_metrics_histogram_max_bins()
99+
{
100+
return NGX_WA_METRICS_HISTOGRAM_BINS_MAX;
101+
}
102+
103+
96104
#endif /* _NGX_WASM_LUA_FFI_H_INCLUDED_ */

0 commit comments

Comments
 (0)