From dc68e4a6477d9912dab52ae838d2ab3abc17a1a3 Mon Sep 17 00:00:00 2001 From: Caio Ramos Casimiro Date: Mon, 12 Aug 2024 21:25:34 +0100 Subject: [PATCH] feat(shm/ffi) expose kv and metrics write operations to Lua land This commit also changes shm-related FFI functions from returning NGX_DECLINED to returning NGX_ABORT, when the necessary conditions for the function completion are not satisfied. --- lib/resty/wasmx/shm.lua | 242 ++++++++++++++++-- src/common/lua/ngx_wasm_lua_ffi.c | 103 ++++++-- src/common/lua/ngx_wasm_lua_ffi.h | 12 +- t/04-openresty/ffi/shm/001-get_zones.t | 27 +- .../ffi/shm/{003-kv_get.t => 101-kv_get.t} | 21 +- t/04-openresty/ffi/shm/102-kv_set.t | 93 +++++++ .../{004-metrics_get.t => 201-metrics_get.t} | 166 +++++++----- t/04-openresty/ffi/shm/202-metrics_define.t | 84 ++++++ t/04-openresty/ffi/shm/203-metrics_record.t | 80 ++++++ .../ffi/shm/204-metrics_increment.t | 74 ++++++ 10 files changed, 771 insertions(+), 131 deletions(-) rename t/04-openresty/ffi/shm/{003-kv_get.t => 101-kv_get.t} (79%) create mode 100644 t/04-openresty/ffi/shm/102-kv_set.t rename t/04-openresty/ffi/shm/{004-metrics_get.t => 201-metrics_get.t} (50%) create mode 100644 t/04-openresty/ffi/shm/202-metrics_define.t create mode 100644 t/04-openresty/ffi/shm/203-metrics_record.t create mode 100644 t/04-openresty/ffi/shm/204-metrics_increment.t diff --git a/lib/resty/wasmx/shm.lua b/lib/resty/wasmx/shm.lua index 786e35c4a..08ca5ddf6 100644 --- a/lib/resty/wasmx/shm.lua +++ b/lib/resty/wasmx/shm.lua @@ -16,8 +16,10 @@ local ngx_log = ngx.log local initialized = false local FFI_DECLINED = wasmx.FFI_DECLINED local FFI_ERROR = wasmx.FFI_ERROR +local FFI_OK = wasmx.FFI_OK local WASM_SHM_KEY = {} -- ensures shm is only locally accessible + local get_zones_handler local types = { @@ -33,6 +35,13 @@ local types = { } } +local metric_type_set = { + [types.ffi_metric.COUNTER] = 1, + [types.ffi_metric.GAUGE] = 2, + [types.ffi_metric.HISTOGRAM] = 3, +} + + local _M = {} @@ -105,13 +114,23 @@ ffi.cdef [[ int ngx_wa_ffi_shm_get_keys(ngx_wa_shm_t *shm, ngx_uint_t n, ngx_str_t **keys); + int ngx_wa_ffi_shm_get_kv_value(ngx_wa_shm_t *shm, ngx_str_t *k, ngx_str_t **v, uint32_t *cas); - int ngx_wa_ffi_shm_get_metric(ngx_str_t *k, + int ngx_wa_ffi_shm_set_kv_value(ngx_wa_shm_t *shm, + ngx_str_t *k, ngx_str_t *v, + uint32_t cas, int *written); + + int ngx_wa_ffi_shm_define_metric(ngx_str_t *name, + ngx_wa_metric_type_e m_type); + int ngx_wa_ffi_shm_record_metric(uint32_t metric_id, int value); + int ngx_wa_ffi_shm_increment_metric(uint32_t metric_id, int value); + int ngx_wa_ffi_shm_get_metric(uint32_t metric_id, ngx_str_t *name, u_char *mbuf, size_t mbs, u_char *hbuf, size_t hbs); + int ngx_wa_ffi_shm_one_slot_metric_size(); int ngx_wa_ffi_shm_max_histogram_size(); ]] @@ -157,17 +176,49 @@ local function get_kv_value(zone, key) local cname = ffi_new("ngx_str_t", { data = key, len = #key }) local cvalue = ffi_new("ngx_str_t *[1]") local ccas = ffi_new("uint32_t [1]") + local pccas = ffi_cast("uint32_t *", ccas) - local rc = C.ngx_wa_ffi_shm_get_kv_value(shm, cname, cvalue, ccas) + local rc = C.ngx_wa_ffi_shm_get_kv_value(shm, cname, cvalue, pccas) if rc == FFI_ERROR then - return nil, "failed retrieving kv value" + return nil, nil, "failed retrieving kv value" + end + + if rc == FFI_DECLINED then + return nil, nil, "key not found" + end + + return ffi_str(cvalue[0].data, cvalue[0].len), tonumber(ccas[0]) +end + + +local function set_kv_value(zone, key, value, cas) + if type(key) ~= "string" then + error("key must be a string", 2) + end + + if type(value) ~= "string" then + error("value must be a string", 2) + end + + if type(cas) ~= "number" then + error("cas must be a number", 2) + end + + local shm = zone[WASM_SHM_KEY] + local cname = ffi_new("ngx_str_t", { data = key, len = #key }) + local cvalue = ffi_new("ngx_str_t", { data = value, len = #value }) + local written = ffi_new("uint32_t [1]") + + local rc = C.ngx_wa_ffi_shm_set_kv_value(shm, cname, cvalue, cas, written) + if rc ~= FFI_OK then + return nil, "failed setting kv value" end if rc == FFI_DECLINED then return nil, "key not found" end - return ffi_str(cvalue[0].data, cvalue[0].len) + return tonumber(written[0]) end @@ -179,27 +230,33 @@ local function new_zero_filled_buffer(ctype, size) end -local function get_metric(zone, name) - if type(name) ~= "string" then - error("name must be a string", 2) - end +local function new_zero_filled_buffer(ctype, size) + local buf = ffi_new(ctype, size) + ffi_fill(buf, size) - local cname = ffi_new("ngx_str_t", { data = name, len = #name }) + return buf +end + + +local function new_metric_buffer() local mbs = C.ngx_wa_ffi_shm_one_slot_metric_size() - local hbs = C.ngx_wa_ffi_shm_max_histogram_size() local mbuf = new_zero_filled_buffer("u_char [?]", mbs) + + return mbuf, mbs +end + +local function new_histogram_buffer() + local hbs = C.ngx_wa_ffi_shm_max_histogram_size() local hbuf = new_zero_filled_buffer("u_char [?]", hbs) - local rc = C.ngx_wa_ffi_shm_get_metric(cname, mbuf, mbs, hbuf, hbs) - if rc == FFI_ERROR then - return nil, "failed retrieving metric" - end + return hbuf, hbs +end - if rc == FFI_DECLINED then - return nil, "metric not found" - end - local cmetric = ffi_cast("ngx_wa_metric_t *", mbuf) +local function parse_cmetric(cmetric) + if type(cmetric) ~= "cdata" then + error("cmetric must be a cdata", 2) + end if cmetric.metric_type == types.ffi_metric.COUNTER then return { type = "counter", value = tonumber(cmetric.slots[0].counter) } @@ -208,6 +265,7 @@ local function get_metric(zone, name) return { type = "gauge", value = tonumber(cmetric.slots[0].gauge.value) } elseif cmetric.metric_type == types.ffi_metric.HISTOGRAM then + local hbuf = cmetric.slots[0].histogram local ch = ffi_cast("ngx_wa_metrics_histogram_t *", hbuf) local h = { type = "histogram", value = {} } @@ -223,6 +281,146 @@ local function get_metric(zone, name) return h end + + return nil, "unknown metric type" +end + + +local function get_metric(zone, metric_id) + if type(metric_id) ~= "number" then + error("mid must be a number", 2) + end + + local mbuf, mbs = new_metric_buffer() + local hbuf, hbs = new_histogram_buffer() + + local rc = C.ngx_wa_ffi_shm_get_metric(metric_id, nil, mbuf, mbs, hbuf, hbs) + if rc == FFI_ERROR then + return nil, "failed retrieving metric" + end + + if rc == FFI_DECLINED then + return nil, "metric not found" + end + + return parse_cmetric(ffi_cast("ngx_wa_metric_t *", mbuf)) +end + + +local function get_metric_by_name(zone, name, opts) + local prefix = true + + if type(name) ~= "string" then + error("name must be a string", 2) + end + + if opts ~= nil then + if type(opts) ~= "table" then + error("opts must be a table", 2) + end + + if opts.prefix ~= nil then + if type(opts.prefix) ~= "boolean" then + error("opts.prefix must be a boolean") + end + + prefix = opts.prefix + end + end + + if prefix then + name = "lua." .. name + end + + local cname = ffi_new("ngx_str_t", { data = name, len = #name }) + local mbuf, mbs = new_metric_buffer() + local hbuf, hbs = new_histogram_buffer() + + local rc = C.ngx_wa_ffi_shm_get_metric(0, cname, mbuf, mbs, hbuf, hbs) + if rc == FFI_ERROR then + return nil, "failed retrieving metric" + end + + if rc == FFI_DECLINED then + return nil, "metric not found" + end + + return parse_cmetric(ffi_cast("ngx_wa_metric_t *", mbuf)) +end + + +local function define_metric(zone, name, metric_type) + if type(name) ~= "string" or name == "" then + error("name must be a non-empty string", 2) + end + + if metric_type_set[metric_type] == nil then + error("metric_type must be either \ + resty.wasmx.shm.metrics.COUNTER, \ + resty.wasmx.shm.metrics.GAUGE or \ + resty.wasmx.shm.metrics.HISTOGRAM", 2) + end + + local cname = ffi_new("ngx_str_t", { data = name, len = #name }) + + local m_id = C.ngx_wa_ffi_shm_define_metric(cname, metric_type) + if m_id == FFI_ERROR then + return nil, "failed defining metric" + end + + return m_id +end + + +local function record_metric(zone, metric_id, value) + if type(metric_id) ~= "number" then + error("metric_id must be a number", 2) + end + + if type(value) ~= "number" then + error("value must be a number", 2) + end + + + local rc = C.ngx_wa_ffi_shm_record_metric(metric_id, value) + if rc == FFI_ERROR then + return nil, "failed recording metric" + end + + if rc == FFI_DECLINED then + return nil, "metric not found" + end + + return true +end + + +local function increment_metric(zone, metric_id, value) + local increment = 1 + + if type(metric_id) ~= "number" then + error("metric_id must be a number", 2) + end + + if value ~= nil then + if type(value) ~= "number" or value < 1 then + error("value must be greater than zero", 2) + end + + increment = value + end + + + local rc = C.ngx_wa_ffi_shm_increment_metric(metric_id, increment) + if rc == FFI_ERROR then + return nil, "failed incrementing metric" + end + + if rc == FFI_DECLINED then + return nil, "metric not found" + end + + return true end @@ -234,13 +432,21 @@ function(shm) if shm.type == types.ffi_shm.SHM_TYPE_KV then _M[zone_name].get_keys = get_keys _M[zone_name].get = get_kv_value + _M[zone_name].set = set_kv_value elseif shm.type == types.ffi_shm.SHM_TYPE_QUEUE then -- NYI elseif shm.type == types.ffi_shm.SHM_TYPE_METRICS then + _M[zone_name].define = define_metric + _M[zone_name].record = record_metric + _M[zone_name].increment = increment_metric _M[zone_name].get_keys = get_keys _M[zone_name].get = get_metric + _M[zone_name].get_by_name = get_metric_by_name + _M[zone_name].COUNTER = types.ffi_metric.COUNTER + _M[zone_name].GAUGE = types.ffi_metric.GAUGE + _M[zone_name].HISTOGRAM = types.ffi_metric.HISTOGRAM end end) diff --git a/src/common/lua/ngx_wasm_lua_ffi.c b/src/common/lua/ngx_wasm_lua_ffi.c index 216261f64..88534076c 100644 --- a/src/common/lua/ngx_wasm_lua_ffi.c +++ b/src/common/lua/ngx_wasm_lua_ffi.c @@ -263,17 +263,12 @@ ngx_int_t ngx_wa_ffi_shm_get_zones(ngx_wa_ffi_shm_get_zones_handler_pt handler) { ngx_uint_t i; - ngx_cycle_t *cycle = (ngx_cycle_t *) ngx_cycle; - ngx_array_t *shms = ngx_wasmx_shms(cycle); + ngx_array_t *shms = ngx_wasmx_shms((ngx_cycle_t *) ngx_cycle); ngx_wa_shm_t *shm; ngx_wa_shm_mapping_t *mappings; - if (!handler) { - return NGX_ERROR; - } - - if (!shms) { - return NGX_DECLINED; + if (!handler || !shms) { + return NGX_ABORT; } mappings = shms->elts; @@ -315,7 +310,7 @@ ngx_wa_ffi_shm_get_keys(ngx_wa_shm_t *shm, ngx_uint_t n, ngx_str_t **keys) ngx_wa_shm_kv_t *kv; if (!shm || shm->type == NGX_WASM_SHM_TYPE_QUEUE) { - return NGX_ERROR; + return NGX_ABORT; } kv = shm->data; @@ -335,7 +330,7 @@ ngx_wa_ffi_shm_get_kv_value(ngx_wa_shm_t *shm, ngx_str_t *k, ngx_str_t **v, uint32_t *cas) { if (!shm || !k || !v || !cas) { - return NGX_ERROR; + return NGX_ABORT; } return ngx_wa_shm_kv_get_locked(shm, k, NULL, v, cas); @@ -343,24 +338,92 @@ ngx_wa_ffi_shm_get_kv_value(ngx_wa_shm_t *shm, ngx_str_t *k, ngx_str_t **v, ngx_int_t -ngx_wa_ffi_shm_get_metric(ngx_str_t *name, - u_char *m_buf, size_t mbs, - u_char *h_buf, size_t hbs) +ngx_wa_ffi_shm_set_kv_value(ngx_wa_shm_t *shm, ngx_str_t *k, ngx_str_t *v, + uint32_t cas, ngx_int_t *written) +{ + if (!shm || !k || !v || !written) { + return NGX_ABORT; + } + + return ngx_wa_shm_kv_set_locked(shm, k, v, cas, written); +} + + +ngx_int_t +ngx_wa_ffi_shm_define_metric(ngx_str_t *name, ngx_wa_metric_type_e type) { uint32_t metric_id; - ngx_wa_metrics_t *metrics; + ngx_int_t rc; + ngx_str_t prefixed_name; + ngx_wa_metrics_t *metrics = ngx_wasmx_metrics((ngx_cycle_t *) ngx_cycle); + + if (!name || !metrics) { + return NGX_ABORT; + } + + u_char buf[metrics->config.max_metric_name_length]; + + prefixed_name.data = buf; + prefixed_name.len = ngx_sprintf(buf, "lua.%V", name) - buf; + + rc = ngx_wa_metrics_define(metrics, &prefixed_name, type, &metric_id); + if (rc != NGX_OK) { + return rc; + } + + return metric_id; +} + + +ngx_int_t +ngx_wa_ffi_shm_increment_metric(uint32_t metric_id, ngx_uint_t value) +{ + ngx_wa_metrics_t *metrics = ngx_wasmx_metrics((ngx_cycle_t *) ngx_cycle); + + if (!metrics) { + return NGX_ABORT; + } + + return ngx_wa_metrics_increment(metrics, metric_id, value); +} + + +ngx_int_t +ngx_wa_ffi_shm_record_metric(uint32_t metric_id, ngx_uint_t value) +{ + ngx_wa_metrics_t *metrics = ngx_wasmx_metrics((ngx_cycle_t *) ngx_cycle); + + if (!metrics) { + return NGX_ABORT; + } + + return ngx_wa_metrics_record(metrics, metric_id, value); +} + + +ngx_int_t +ngx_wa_ffi_shm_get_metric(uint32_t metric_id, ngx_str_t *name, + u_char *m_buf, size_t mbs, u_char *h_buf, size_t hbs) +{ + ngx_wa_metrics_t *metrics = ngx_wasmx_metrics((ngx_cycle_t *) ngx_cycle); ngx_wa_metric_t *m; - if (!name || !m_buf || !h_buf) { - return NGX_ERROR; + if ((!name && !metric_id) + || !m_buf || mbs < NGX_WA_METRICS_ONE_SLOT_METRIC_SIZE + || !h_buf || hbs < NGX_WA_METRICS_MAX_HISTOGRAM_SIZE + || !metrics) + { + return NGX_ABORT; } - metrics = ngx_wasmx_metrics((ngx_cycle_t *) ngx_cycle); - metric_id = ngx_crc32_long(name->data, name->len); m = (ngx_wa_metric_t *) m_buf; - ngx_wa_metrics_set_histogram_buffer(m, h_buf, hbs); - return ngx_wa_metrics_get(metrics, metric_id, m); + if (metric_id) { + return ngx_wa_metrics_get(metrics, metric_id, m); + } + + return ngx_wa_metrics_get(metrics, + ngx_crc32_long(name->data, name->len), m); } #endif diff --git a/src/common/lua/ngx_wasm_lua_ffi.h b/src/common/lua/ngx_wasm_lua_ffi.h index 7df08c11f..f6c809b0d 100644 --- a/src/common/lua/ngx_wasm_lua_ffi.h +++ b/src/common/lua/ngx_wasm_lua_ffi.h @@ -51,8 +51,16 @@ ngx_int_t ngx_wa_ffi_shm_get_keys(ngx_wa_shm_t *shm, ngx_uint_t n, ngx_str_t **keys); ngx_int_t ngx_wa_ffi_shm_get_kv_value(ngx_wa_shm_t *shm, ngx_str_t *k, ngx_str_t **v, uint32_t *cas); -ngx_int_t ngx_wa_ffi_shm_get_metric(ngx_str_t *k, u_char *m_buf, size_t mbs, - u_char *h_buf, size_t hbs); +ngx_int_t ngx_wa_ffi_shm_set_kv_value(ngx_wa_shm_t *shm, ngx_str_t *k, + ngx_str_t *v, uint32_t cas, ngx_int_t *written); + + +ngx_int_t ngx_wa_ffi_shm_define_metric(ngx_str_t *name, + ngx_wa_metric_type_e type); +ngx_int_t ngx_wa_ffi_shm_increment_metric(uint32_t metric_id, ngx_uint_t value); +ngx_int_t ngx_wa_ffi_shm_record_metric(uint32_t metric_id, ngx_uint_t value); +ngx_int_t ngx_wa_ffi_shm_get_metric(uint32_t metric_id, ngx_str_t *name, + u_char *m_buf, size_t mbs, u_char *h_buf, size_t hbs); ngx_int_t diff --git a/t/04-openresty/ffi/shm/001-get_zones.t b/t/04-openresty/ffi/shm/001-get_zones.t index 349389ed7..5e8775c30 100644 --- a/t/04-openresty/ffi/shm/001-get_zones.t +++ b/t/04-openresty/ffi/shm/001-get_zones.t @@ -17,20 +17,23 @@ get_zones() is silently called when resty.wasmx.shm module is loaded. --- valgrind --- load_nginx_modules: ngx_http_echo_module ---- wasm_modules: hostcalls ---- shm_kv: kv1 1m ---- shm_queue: q1 1m +--- main_config + wasm { + shm_kv kv1 1m; + shm_queue q1 1m; + } + --- config location /t { access_by_lua_block { local shm = require "resty.wasmx.shm" - assert(shm.kv1 ~= nil) - assert(shm.q1 ~= nil) - assert(shm.metrics ~= nil) - } + assert(shm.kv1) + assert(shm.q1) + assert(shm.metrics) - echo ok; + ngx.say("ok") + } } --- response_body ok @@ -45,26 +48,24 @@ ok === TEST 2: shm - get_zones(), no zones --- valgrind ---- load_nginx_modules: ngx_http_echo_module --- config location /t { access_by_lua_block { local shm = require "resty.wasmx.shm" assert(shm.metrics == nil) - } - echo ok; + ngx.say("ok") + } } --- response_body ok ---- error_log eval -qr/\[info\] .*? no shm zones found/, --- no_error_log [error] [crit] [emerg] [alert] +[stub] diff --git a/t/04-openresty/ffi/shm/003-kv_get.t b/t/04-openresty/ffi/shm/101-kv_get.t similarity index 79% rename from t/04-openresty/ffi/shm/003-kv_get.t rename to t/04-openresty/ffi/shm/101-kv_get.t index 9d33fd6e9..45185a176 100644 --- a/t/04-openresty/ffi/shm/003-kv_get.t +++ b/t/04-openresty/ffi/shm/101-kv_get.t @@ -14,7 +14,6 @@ __DATA__ === TEST 1: shm_kv - get() --- valgrind ---- load_nginx_modules: ngx_http_echo_module --- wasm_modules: hostcalls --- shm_kv: kv1 1m --- config @@ -30,22 +29,28 @@ __DATA__ access_by_lua_block { local shm = require "resty.wasmx.shm" - local kv1 = shm.kv1 - local keys = kv1:get_keys() + local str_format = string.format + local keys = shm.kv1:get_keys() for i, k in ipairs(keys) do - local v = kv1:get(k) - ngx.log(ngx.INFO,"kv1 " .. k .. ": ", v) + local v, cas, err = shm.kv1:get(k) + + if not v then + ngx.say(err) + end + + ngx.log(ngx.INFO, str_format("kv1 %s: %s, cas: %d", k, v, cas)) end + + ngx.say("ok") } - echo ok; } --- response_body ok --- error_log eval [ - qr/\[info\] .*? kv1 kv1\/k1: hello1/, - qr/\[info\] .*? kv1 kv1\/k2: hello2/, + qr/\[info\] .*? kv1 kv1\/k1: hello1, cas: 1/, + qr/\[info\] .*? kv1 kv1\/k2: hello2, cas: 1/, ] --- no_error_log [error] diff --git a/t/04-openresty/ffi/shm/102-kv_set.t b/t/04-openresty/ffi/shm/102-kv_set.t new file mode 100644 index 000000000..fb7b18998 --- /dev/null +++ b/t/04-openresty/ffi/shm/102-kv_set.t @@ -0,0 +1,93 @@ +# vim:set ft= ts=4 sts=4 sw=4 et fdm=marker: + +use strict; +use lib '.'; +use t::TestWasmX; +use t::TestWasmX::Lua; + +skip_no_openresty(); + +plan_tests(7); +run_tests(); + +__DATA__ + +=== TEST 1: shm_kv - set() +--- valgrind +--- shm_kv: kv1 1m +--- wasm_modules: on_phases +--- config + location /t { + access_by_lua_block { + local shm = require "resty.wasmx.shm" + + local str_fmt = string.format + local k = "a_key" + local v = "a_value" + local cas = 0 + + local written, err = shm.kv1:set(k, v, cas) + if not written then + ngx.say(err) + return + end + + ngx.log(ngx.INFO, str_fmt("written: %d", written)) + assert(written == 1) + + local retrieved_v, cas, err = shm.kv1:get(k) + if not retrieved_v then + ngx.say(err) + return + end + + ngx.log(ngx.INFO, str_fmt("kv1 %s: %s, cas: %d", k, retrieved_v, cas)) + + ngx.say("ok") + } + } +--- response_body +ok +--- error_log eval +qr/\[info\] .*? kv1 a_key: a_value, cas: 1/ +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 2: shm_kv - set(), bad pointer +A call with a bad pointer is simply ignored. + +--- valgrind +--- config + location /t { + access_by_lua_block { + local ffi = require "ffi" + local C = ffi.C + + ffi.cdef [[ + typedef struct ngx_wa_shm_t ngx_wa_shm_t; + + int ngx_wa_ffi_shm_set_kv_value(ngx_wa_shm_t *shm, + ngx_str_t *k, + ngx_str_t *v, + uint32_t cas, + int *written); + ]] + + C.ngx_wa_ffi_shm_set_kv_value(nil, nil, nil, 0, nil) + + ngx.say("ok") + } + } +--- response_body +ok +--- no_error_log +[error] +[crit] +[emerg] +[alert] +[stub] diff --git a/t/04-openresty/ffi/shm/004-metrics_get.t b/t/04-openresty/ffi/shm/201-metrics_get.t similarity index 50% rename from t/04-openresty/ffi/shm/004-metrics_get.t rename to t/04-openresty/ffi/shm/201-metrics_get.t index c47aab848..0d5042e05 100644 --- a/t/04-openresty/ffi/shm/004-metrics_get.t +++ b/t/04-openresty/ffi/shm/201-metrics_get.t @@ -7,14 +7,55 @@ use t::TestWasmX::Lua; skip_no_openresty(); -plan_tests(9); +plan_tests(6); run_tests(); __DATA__ -=== TEST 1: shm_metrics - get(), sanity +=== TEST 1: shm_metrics - get, by name +--- valgrind +--- main_config + wasm {} +--- config + location /t { + access_by_lua_block { + local pretty = require "pl.pretty" + local shm = require "resty.wasmx.shm" + + local pwrite = pretty.write + local str_format = string.format + + local buf = "" + local c1 = shm.metrics:define("c1", shm.metrics.COUNTER) + local g1 = shm.metrics:define("g1", shm.metrics.GAUGE) + local h1 = shm.metrics:define("h1", shm.metrics.HISTOGRAM) + + shm.metrics:increment(c1) + shm.metrics:record(g1, 10) + shm.metrics:record(h1, 100) + + for i, name in pairs({ "c1", "g1", "h1" }) do + local m = shm.metrics:get_by_name(name) + buf = str_format("%s\n%s: %s", buf, name, pwrite(m, "")) + end + + ngx.say(buf:sub(2)) + } + } +--- response_body +c1: {type="counter",value=1} +g1: {type="gauge",value=10} +h1: {type="histogram",value={[4294967295]=0,[128]=1}} +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 2: shm_metrics - get, by name (prefix = false) --- valgrind ---- load_nginx_modules: ngx_http_echo_module --- wasm_modules: hostcalls --- config location /t { @@ -45,30 +86,25 @@ __DATA__ local pretty = require "pl.pretty" local shm = require "resty.wasmx.shm" - local metrics = shm.metrics - - local keys = { + local buf = "" + local names = { "pw.hostcalls.c1", "pw.hostcalls.g1", "pw.hostcalls.h1" } - for i, k in pairs(keys) do - local m = metrics:get(k) - ngx.log(ngx.INFO, k .. ": " .. pretty.write(m, "")) + for i, n in pairs(names) do + local m = shm.metrics:get_by_name(n, { prefix = false }) + buf = string.format("%s\n%s: %s", buf, n, pretty.write(m, "")) end - } - echo ok; + ngx.say(buf:sub(2)) + } } --- response_body -ok ---- error_log eval -[ - qr/\[info\] .*? pw\.hostcalls\.c1: \{type="counter",value=13}/, - qr/\[info\] .*? pw\.hostcalls\.g1: \{type="gauge",value=1}/, - qr/\[info\] .*? pw\.hostcalls\.h1: \{type="histogram",value=\{\[4294967295]=0,\[128]=1,\[16]=1}}/, -] +pw.hostcalls.c1: {type="counter",value=13} +pw.hostcalls.g1: {type="gauge",value=1} +pw.hostcalls.h1: {type="histogram",value={[4294967295]=0,[128]=1,[16]=1}} --- no_error_log [error] [crit] @@ -77,10 +113,10 @@ ok -=== TEST 2: shm_metrics - get(), invalid name +=== TEST 3: shm_metrics - get, by name (invalid name) --- valgrind ---- load_nginx_modules: ngx_http_echo_module ---- wasm_modules: hostcalls +--- main_config + wasm {} --- config location /t { access_by_lua_block { @@ -90,67 +126,50 @@ ok local metrics = shm.metrics local k = "invalid_name" - local m = metrics:get(k) + local m, err = metrics:get_by_name(k) - ngx.log(ngx.INFO, k .. ": " .. pretty.write(m, "")) + if not m then + ngx.say(err) + end } - - echo ok; } --- response_body -ok ---- error_log eval -[ - qr/\[info\] .*? invalid_name: nil/, -] +metric not found --- no_error_log [error] [crit] [emerg] [alert] -[stub] -[stub] -=== TEST 3: shm_metrics - get(), non-initialized +=== TEST 4: shm_metrics - get, bad pointer +A call with bad a pointer is simply ignored. + --- valgrind ---- load_nginx_modules: ngx_http_echo_module ---- wasm_modules: hostcalls +--- main_config + wasm {} --- config location /t { - proxy_wasm hostcalls 'on_configure=define_metrics \ - on=request_headers \ - metrics=c1,g1,h1'; - access_by_lua_block { - local pretty = require "pl.pretty" - local shm = require "resty.wasmx.shm" + local ffi = require "ffi" + local C = ffi.C - local metrics = shm.metrics + ffi.cdef [[ + typedef unsigned char u_char; - local keys = { - "pw.hostcalls.c1", - "pw.hostcalls.g1", - "pw.hostcalls.h1" - } + int ngx_wa_ffi_shm_get_metric(ngx_str_t *k, + u_char *mbuf, size_t mbs, + u_char *hbuf, size_t hbs); + ]] - for i, k in pairs(keys) do - local m = metrics:get(k) - ngx.log(ngx.INFO, k .. ": " .. pretty.write(m, "")) - end - } + C.ngx_wa_ffi_shm_get_metric(nil, nil, 0, nil, 0) - echo ok; + ngx.say("ok") + } } --- response_body ok ---- error_log eval -[ - qr/\[info\] .*? pw\.hostcalls\.c1: \{type="counter",value=0}/, - qr/\[info\] .*? pw\.hostcalls\.g1: \{type="gauge",value=0}/, - qr/\[info\] .*? pw\.hostcalls\.h1: \{type="histogram",value=\{\[4294967295]=0}}/, -] --- no_error_log [error] [crit] @@ -159,19 +178,27 @@ ok -=== TEST 4: shm_metrics - get(), bad pointer -A call with bad a pointer is simply ignored. +=== TEST 5: shm_metrics - get, out of bounds write +Inducing an out of bounds write triggers a SIGSEGV signal to Nginx, which +terminates it. ---- valgrind ---- load_nginx_modules: ngx_http_echo_module ---- wasm_modules: hostcalls ---- shm_kv: kv1 1m +--- main_config + wasm {} --- config location /t { access_by_lua_block { local ffi = require "ffi" local C = ffi.C + local name = "c1" + local cname = ffi.new("ngx_str_t", { data = name, len = #name }) + + local hbs = 1 + local hbuf = ffi.new("unsigned char [?]", hbs) + + local mbs = 1 + local mbuf = ffi.new("unsigned char [?]", mbs) + ffi.cdef [[ typedef unsigned char u_char; @@ -180,10 +207,10 @@ A call with bad a pointer is simply ignored. u_char *hbuf, size_t hbs); ]] - C.ngx_wa_ffi_shm_get_metric(nil, nil, 0, nil, 0) - } + C.ngx_wa_ffi_shm_get_metric(cname, mbuf, mbs + 20, hbuf, hbs + 20) - echo ok; + ngx.say("ok") + } } --- response_body ok @@ -193,5 +220,4 @@ ok [emerg] [alert] [stub] -[stub] -[stub] +--- must_die: 0 diff --git a/t/04-openresty/ffi/shm/202-metrics_define.t b/t/04-openresty/ffi/shm/202-metrics_define.t new file mode 100644 index 000000000..c3d27c2eb --- /dev/null +++ b/t/04-openresty/ffi/shm/202-metrics_define.t @@ -0,0 +1,84 @@ +# vim:set ft= ts=4 sts=4 sw=4 et fdm=marker: + +use strict; +use lib '.'; +use t::TestWasmX; +use t::TestWasmX::Lua; + +skip_no_openresty(); + +plan_tests(9); +run_tests(); + +__DATA__ + +=== TEST 1: shm_metrics - define() +--- valgrind +--- main_config + wasm {} +--- config + location /t { + access_by_lua_block { + local shm = require "resty.wasmx.shm" + + local metrics = shm.metrics + + metrics:define("c1", metrics.COUNTER) + metrics:define("g1", metrics.GAUGE) + metrics:define("h1", metrics.HISTOGRAM) + + ngx.say("ok") + } + } +--- response_body +ok +--- error_log eval +[ + qr/.*? \[info\] .*? defined counter "lua.c1" with id \d+/, + qr/.*? \[info\] .*? defined gauge "lua.g1" with id \d+/, + qr/.*? \[info\] .*? defined histogram "lua.h1" with id \d+/, +] +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 2: shm_metrics - define(), bad FFI args +A call with bad a pointer is simply ignored. + +--- skip_no_debug +--- valgrind +--- main_config + wasm {} +--- config + location /t { + access_by_lua_block { + local ffi = require "ffi" + local C = ffi.C + + ffi.cdef [[ + int ngx_wa_ffi_shm_define_metric(ngx_str_t *k, int m_type); + ]] + + local name = "c1" + local cname = ffi.new("ngx_str_t", { data = name, len = #name }) + + C.ngx_wa_ffi_shm_define_metric(cname, 10) + C.ngx_wa_ffi_shm_define_metric(nil, 0) + + ngx.say("ok") + } + } +--- response_body +ok +--- no_error_log +[error] +[crit] +[emerg] +[alert] +[stub] +[stub] +[stub] diff --git a/t/04-openresty/ffi/shm/203-metrics_record.t b/t/04-openresty/ffi/shm/203-metrics_record.t new file mode 100644 index 000000000..1439eade0 --- /dev/null +++ b/t/04-openresty/ffi/shm/203-metrics_record.t @@ -0,0 +1,80 @@ +# vim:set ft= ts=4 sts=4 sw=4 et fdm=marker: + +use strict; +use lib '.'; +use t::TestWasmX; +use t::TestWasmX::Lua; + +skip_no_openresty(); + +plan_tests(6); +run_tests(); + +__DATA__ + +=== TEST 1: shm_metrics - record() +--- valgrind +--- main_config + wasm {} +--- config + location /t { + access_by_lua_block { + local pretty = require "pl.pretty" + local shm = require "resty.wasmx.shm" + + local pwrite = pretty.write + local str_format = string.format + + local buf = "" + local g1 = shm.metrics:define("g1", shm.metrics.GAUGE) + local h1 = shm.metrics:define("h1", shm.metrics.HISTOGRAM) + + shm.metrics:record(g1, 10) + shm.metrics:record(h1, 100) + + buf = str_format("%s\ng1: %s", buf, pwrite(shm.metrics:get(g1), "")) + buf = str_format("%s\nh1: %s", buf, pwrite(shm.metrics:get(h1), "")) + + ngx.say(buf:sub(2)) + } + } +--- response_body +g1: {type="gauge",value=10} +h1: {type="histogram",value={[4294967295]=0,[128]=1}} +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 2: shm_metrics - record(), inexistent metric +--- valgrind +--- main_config + wasm {} +--- config + location /t { + access_by_lua_block { + local wasmx = require "resty.wasmx" + local shm = require "resty.wasmx.shm" + + local FFI_DECLINED = wasmx.FFI_DECLINED + local metrics = shm.metrics + + local ok, err = metrics:record(1, 10) + + if not ok then + return ngx.say(err) + end + + ngx.say("fail") + } + } +--- response_body +metric not found +--- no_error_log +[error] +[crit] +[emerg] +[alert] diff --git a/t/04-openresty/ffi/shm/204-metrics_increment.t b/t/04-openresty/ffi/shm/204-metrics_increment.t new file mode 100644 index 000000000..50e1871a6 --- /dev/null +++ b/t/04-openresty/ffi/shm/204-metrics_increment.t @@ -0,0 +1,74 @@ +# vim:set ft= ts=4 sts=4 sw=4 et fdm=marker: + +use strict; +use lib '.'; +use t::TestWasmX; +use t::TestWasmX::Lua; + +skip_no_openresty(); + +plan_tests(6); +run_tests(); + +__DATA__ + +=== TEST 1: shm_metrics - increment() +--- valgrind +--- main_config + wasm {} +--- config + location /t { + access_by_lua_block { + local pretty = require "pl.pretty" + local shm = require "resty.wasmx.shm" + + local pwrite = pretty.write + local str_format = string.format + + local c1 = shm.metrics:define("c1", shm.metrics.COUNTER) + + shm.metrics:increment(c1) + shm.metrics:increment(c1, 10) + + ngx.say(str_format("c1: %s", pwrite(shm.metrics:get(c1), ""))) + } + } +--- response_body +c1: {type="counter",value=11} +--- no_error_log +[error] +[crit] +[emerg] +[alert] + + + +=== TEST 2: shm_metrics - increment(), inexistent metric +--- valgrind +--- main_config + wasm {} +--- config + location /t { + access_by_lua_block { + local wasmx = require "resty.wasmx" + local shm = require "resty.wasmx.shm" + + local FFI_DECLINED = wasmx.FFI_DECLINED + local metrics = shm.metrics + + local ok, err = metrics:increment(1, 10) + + if not ok then + return ngx.say(err) + end + + ngx.say("fail") + } + } +--- response_body +metric not found +--- no_error_log +[error] +[crit] +[emerg] +[alert]