diff --git a/Makefile b/Makefile index 49468dc57e41..0eb4864aa49a 100644 --- a/Makefile +++ b/Makefile @@ -300,6 +300,9 @@ install: runtime $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/include/apisix/model $(ENV_INSTALL) apisix/include/apisix/model/*.proto $(ENV_INST_LUADIR)/apisix/include/apisix/model/ + $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/inspect + $(ENV_INSTALL) apisix/inspect/*.lua $(ENV_INST_LUADIR)/apisix/inspect/ + $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins $(ENV_INSTALL) apisix/plugins/*.lua $(ENV_INST_LUADIR)/apisix/plugins/ diff --git a/apisix/inspect/dbg.lua b/apisix/inspect/dbg.lua new file mode 100644 index 000000000000..a8a619a261ac --- /dev/null +++ b/apisix/inspect/dbg.lua @@ -0,0 +1,151 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +local core = require("apisix.core") +local string_format = string.format +local debug = debug +local ipairs = ipairs +local pcall = pcall +local table_insert = table.insert +local jit = jit + +local _M = {} + +local hooks = {} + +function _M.getname(n) + if n.what == "C" then + return n.name + end + local lc = string_format("%s:%d", n.short_src, n.currentline) + if n.what ~= "main" and n.namewhat ~= "" then + return string_format("%s (%s)", lc, n.name) + else + return lc + end +end + +local function hook(_, arg) + local level = 2 + local finfo = debug.getinfo(level, "nSlf") + local key = finfo.source .. "#" .. arg + + local hooks2 = {} + for _, hook in ipairs(hooks) do + if key:sub(-#hook.key) == hook.key then + local filter_func = hook.filter_func + local info = {finfo = finfo, uv = {}, vals = {}} + + -- upvalues + local i = 1 + while true do + local name, value = debug.getupvalue(finfo.func, i) + if name == nil then break end + if name:sub(1, 1) ~= "(" then + info.uv[name] = value + end + i = i + 1 + end + + -- local values + local i = 1 + while true do + local name, value = debug.getlocal(level, i) + if not name then break end + if name:sub(1, 1) ~= "(" then + info.vals[name] = value + end + i = i + 1 + end + + local r1, r2_or_err = pcall(filter_func, info) + if not r1 then + core.log.error("inspect: pcall filter_func:", r2_or_err) + elseif r2_or_err == false then + -- if filter_func returns false, keep the hook + table_insert(hooks2, hook) + end + else + -- key not match, keep the hook + table_insert(hooks2, hook) + end + end + + -- disable debug mode if all hooks done + if #hooks2 ~= #hooks then + hooks = hooks2 + if #hooks == 0 then + debug.sethook() + end + end +end + +function _M.set_hook(file, line, func, filter_func) + if file == nil then + file = "=stdin" + end + + local key = file .. "#" .. line + table_insert(hooks, {key = key, filter_func = filter_func}) + + if jit then + jit.flush(func) + jit.off() + end + + debug.sethook(hook, "l") +end + +function _M.unset_hook(file, line) + if file == nil then + file = "=stdin" + end + + local hooks2 = {} + + local key = file .. "#" .. line + for i, hook in ipairs(hooks) do + if hook.key ~= key then + table_insert(hooks2, hook) + end + end + + if #hooks2 ~= #hooks then + hooks = hooks2 + if #hooks == 0 then + debug.sethook() + if jit then + jit.on() + end + end + end +end + +function _M.unset_all() + if #hooks > 0 then + hooks = {} + debug.sethook() + if jit then + jit.on() + end + end +end + +function _M.hooks() + return hooks +end + +return _M diff --git a/apisix/inspect/init.lua b/apisix/inspect/init.lua new file mode 100644 index 000000000000..a33c30ec3c5b --- /dev/null +++ b/apisix/inspect/init.lua @@ -0,0 +1,128 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +local core = require("apisix.core") +local dbg = require("apisix.inspect.dbg") +local lfs = require("lfs") +local pl_path = require("pl.path") +local io = io +local table_insert = table.insert +local pcall = pcall +local ipairs = ipairs +local os = os +local ngx = ngx +local loadstring = loadstring +local format = string.format + +local _M = {} + +local last_modified = 0 + +local stop = false + +local running = false + +local last_report_time = 0 + +local REPORT_INTERVAL = 30 -- secs + +local function run_lua_file(file) + local f, err = io.open(file, "rb") + if not f then + return false, err + end + local code, err = f:read("*all") + f:close() + if code == nil then + return false, format("cannot read hooks file: %s", err) + end + local func, err = loadstring(code) + if not func then + return false, err + end + func() + return true +end + +local function setup_hooks(file) + if pl_path.exists(file) then + dbg.unset_all() + local _, err = pcall(run_lua_file, file) + local hooks = {} + for _, hook in ipairs(dbg.hooks()) do + table_insert(hooks, hook.key) + end + core.log.info("set hooks: err: ", err, ", hooks: ", core.json.delay_encode(hooks)) + end +end + +local function reload_hooks(premature, delay, file) + if premature or stop then + stop = false + running = false + return + end + + local time, err = lfs.attributes(file, 'modification') + if err then + if last_modified ~= 0 then + core.log.info(err, ", disable all hooks") + dbg.unset_all() + last_modified = 0 + end + elseif time ~= last_modified then + setup_hooks(file) + last_modified = time + else + local ts = os.time() + if ts - last_report_time >= REPORT_INTERVAL then + local hooks = {} + for _, hook in ipairs(dbg.hooks()) do + table_insert(hooks, hook.key) + end + core.log.info("alive hooks: ", core.json.encode(hooks)) + last_report_time = ts + end + end + + local ok, err = ngx.timer.at(delay, reload_hooks, delay, file) + if not ok then + core.log.error("failed to create the timer: ", err) + running = false + end +end + +function _M.init(delay, file) + if not running then + file = file or "/var/run/apisix_inspect_hooks.lua" + delay = delay or 3 + + setup_hooks(file) + + local ok, err = ngx.timer.at(delay, reload_hooks, delay, file) + if not ok then + core.log.error("failed to create the timer: ", err) + return + end + running = true + end +end + +function _M.destroy() + stop = true +end + +return _M diff --git a/apisix/plugins/inspect.lua b/apisix/plugins/inspect.lua new file mode 100644 index 000000000000..19f50c79e55b --- /dev/null +++ b/apisix/plugins/inspect.lua @@ -0,0 +1,61 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +local core = require("apisix.core") +local plugin = require("apisix.plugin") +local inspect = require("apisix.inspect") + + +local plugin_name = "inspect" + + +local schema = { + type = "object", + properties = {}, +} + + +local _M = { + version = 0.1, + priority = 200, + name = plugin_name, + schema = schema, +} + + +function _M.check_schema(conf, schema_type) + return core.schema.check(schema, conf) +end + + +function _M.init() + local attr = plugin.plugin_attr(plugin_name) + local delay + local hooks_file + if attr then + delay = attr.delay + hooks_file = attr.hooks_file + end + core.log.info("delay=", delay, ", hooks_file=", hooks_file) + return inspect.init(delay, hooks_file) +end + + +function _M.destroy() + return inspect.destroy() +end + +return _M diff --git a/conf/config-default.yaml b/conf/config-default.yaml index 6e714b577346..5cbf737353cd 100755 --- a/conf/config-default.yaml +++ b/conf/config-default.yaml @@ -461,6 +461,7 @@ plugins: # plugin list (sorted by priority) - file-logger # priority: 399 - clickhouse-logger # priority: 398 - tencent-cloud-cls # priority: 397 + - inspect # priority: 200 #- log-rotate # priority: 100 # <- recommend to use priority (0, 100) for your custom plugins - example-plugin # priority: 0 @@ -555,6 +556,9 @@ plugin_attr: send: 60s # redirect: # https_port: 8443 # the default port for use by HTTP redirects to HTTPS + inspect: + delay: 3 # in seconds + hooks_file: "/usr/local/apisix/plugin_inspect_hooks.lua" deployment: role: traditional diff --git a/docs/assets/images/plugin/inspect.png b/docs/assets/images/plugin/inspect.png new file mode 100644 index 000000000000..efe82eed8266 Binary files /dev/null and b/docs/assets/images/plugin/inspect.png differ diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json index ae6dd95d4296..4b43afc37b98 100644 --- a/docs/en/latest/config.json +++ b/docs/en/latest/config.json @@ -57,7 +57,8 @@ "plugins/server-info", "plugins/ext-plugin-pre-req", "plugins/ext-plugin-post-req", - "plugins/ext-plugin-post-resp" + "plugins/ext-plugin-post-resp", + "plugins/inspect" ] }, { diff --git a/docs/en/latest/plugins/inspect.md b/docs/en/latest/plugins/inspect.md new file mode 100644 index 000000000000..fad20a1bb1ba --- /dev/null +++ b/docs/en/latest/plugins/inspect.md @@ -0,0 +1,171 @@ +--- +title: inspect +keywords: + - APISIX + - Plugin + - Inspect + - Dynamic Lua Debugging +description: This document contains information about the Apache APISIX inspect Plugin. +--- + + + +## Description + +It's useful to set arbitrary breakpoint in any Lua file to inspect the context information, +e.g. print local variables if some condition satisfied. + +In this way, you don't need to modify the source code of your project, and just get diagnose information +on demand, i.e. dynamic logging. + +This plugin supports setting breakpoints within both interpretd function and jit compiled function. +The breakpoint could be at any position within the function. The function could be global/local/module/ananymous. + +## Features + +* Set breakpoint at any position +* Dynamic breakpoint +* customized breakpoint handler +* You could define one-shot breakpoint +* Work for jit compiled function +* If function reference specified, then performance impact is only bound to that function (JIT compiled code will not trigger debug hook, so they would run fast even if hook is enabled) +* If all breakpoints deleted, jit could recover + +## Operation Graph + +![Operation Graph](https://raw.githubusercontent.com/apache/apisix/master/docs/assets/images/plugin/inspect.png) + +## API to define hook in hooks file + +### require("resty.inspect.dbg").set_hook(file, line, func, filter_func) + +The breakpoint is specified by `file` (full qualified or short file name) and the `line` number. + +The `func` specified the scope (which function or global) of jit cache to flush: + +* If the breakpoint is related to a module function or +global function, you should set it that function reference, then only the jit cache of that function would +be flushed, and it would not affect other caches to avoid slowing down other parts of the program. + +* If the breakpointis related to local function or anonymous function, +then you have to set it to `nil` (because no way to get function reference), which would flush the whole jit cache of Lua vm. + +You attach a `filter_func` function of the breakpoint, the function takes the `info` as argument and returns +true of false to determine whether the breakpoint would be removed. You could setup one-shot breakpoint +at ease. + +The `info` is a hash table which contains below keys: + +* `finfo`: `debug.getinfo(level, "nSlf")` +* `uv`: upvalues hash table +* `vals`: local variables hash table + +## Attributes + +| Name | Type | Required | Default | Description | +|--------------------|---------|----------|---------|------------------------------------------------------------------------------------------------| +| delay | integer | False | 3 | Time in seconds specifying how often to check the hooks file. | +| hooks_file | string | False | "/usr/local/apisix/plugin_inspect_hooks.lua" | Lua file to define hooks, which could be a link file. Ensure only administrator could write this file, otherwise it may be a security risk. | + +## Enabling the Plugin + +Plugin is enabled by default (`conf/config-default.yaml`): + +```yaml title="conf/config-default.yaml" +plugins: + - inspect + +plugin_attr: + inspect: + delay: 3 + hooks_file: "/usr/local/apisix/plugin_inspect_hooks.lua" +``` + +## Example usage + +```bash +# create test route +curl http://127.0.0.1:9180/apisix/admin/routes/test_limit_req -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "methods": ["GET"], + "uri": "/get", + "plugins": { + "limit-req": { + "rate": 100, + "burst": 0, + "rejected_code": 503, + "key_type": "var", + "key": "remote_addr" + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "httpbin.org": 1 + } + } +}' + +# create a hooks file to set a test breakpoint +# Note that the breakpoint is associated with the line number, +# so if the Lua code changes, you need to adjust the line number in the hooks file +cat </usr/local/apisix/example_hooks.lua +local dbg = require "resty.inspect.dbg" + +dbg.set_hook("limit-req.lua", 88, require("apisix.plugins.limit-req").access, function(info) + ngx.log(ngx.INFO, debug.traceback("foo traceback", 3)) + ngx.log(ngx.INFO, dbg.getname(info.finfo)) + ngx.log(ngx.INFO, "conf_key=", info.vals.conf_key) + return true +end) + +--- more breakpoints could be defined via dbg.set_hook() +--- ... +EOF + +# enable the hooks file +ln -sf /usr/local/apisix/example_hooks.lua /usr/local/apisix/plugin_inspect_hooks.lua + +# check errors.log to confirm the test breakpoint is enabled +2022/09/01 00:55:38 [info] 2754534#2754534: *3700 [lua] init.lua:29: setup_hooks(): set hooks: err=nil, hooks=["limit-req.lua#88"], context: ngx.timer + +# access the test route +curl -i http://127.0.0.1:9080/get + +# check errors.log to confirm the test breakpoint is triggered +2022/09/01 00:55:52 [info] 2754534#2754534: *4070 [lua] resty_inspect_hooks.lua:4: foo traceback +stack traceback: + /opt/lua-resty-inspect/lib/resty/inspect/dbg.lua:50: in function + /opt/apisix.fork/apisix/plugins/limit-req.lua:88: in function 'phase_func' + /opt/apisix.fork/apisix/plugin.lua:900: in function 'run_plugin' + /opt/apisix.fork/apisix/init.lua:456: in function 'http_access_phase' + access_by_lua(nginx.conf:303):2: in main chunk, client: 127.0.0.1, server: _, request: "GET /get HTTP/1.1", host: "127.0.0.1:9080" +2022/09/01 00:55:52 [info] 2754534#2754534: *4070 [lua] resty_inspect_hooks.lua:5: /opt/apisix.fork/apisix/plugins/limit-req.lua:88 (phase_func), client: 127.0.0.1, server: _, request: "GET /get HTTP/1.1", host: "127.0.0.1:9080" +2022/09/01 00:55:52 [info] 2754534#2754534: *4070 [lua] resty_inspect_hooks.lua:6: conf_key=remote_addr, client: 127.0.0.1, server: _, request: "GET /get HTTP/1.1", host: "127.0.0.1:9080" +``` + +## Disable plugin + +To remove the `inspect` Plugin, you can remove it from your configuration file (`conf/config.yaml`): + +```yaml title="conf/config.yaml" +plugins: + # - inspect +``` diff --git a/t/admin/plugins.t b/t/admin/plugins.t index 2ab993152fcd..5d3e88f7db24 100644 --- a/t/admin/plugins.t +++ b/t/admin/plugins.t @@ -123,6 +123,7 @@ udp-logger file-logger clickhouse-logger tencent-cloud-cls +inspect example-plugin aws-lambda azure-functions diff --git a/t/lib/test_inspect.lua b/t/lib/test_inspect.lua new file mode 100644 index 000000000000..62de5993008d --- /dev/null +++ b/t/lib/test_inspect.lua @@ -0,0 +1,62 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +-- +-- Don't edit existing code, because the hooks are identified by line number. +-- Instead, append new code to this file. +-- +local _M = {} + +function _M.run1() + local var1 = "hello" + local var2 = "world" + return var1 .. var2 +end + +local upvar1 = 2 +local upvar2 = "yes" +function _M.run2() + return upvar1 +end + +function _M.run3() + return upvar1 .. upvar2 +end + +local str = string.rep("a", 8192) .. "llzz" + +local sk = require("socket") + +function _M.hot1() + local t1 = sk.gettime() + for i=1,100000 do + string.find(str, "ll", 1, true) + end + local t2 = sk.gettime() + return t2 - t1 +end + +function _M.hot2() + local t1 = sk.gettime() + for i=1,100000 do + string.find(str, "ll", 1, true) + end + local t2 = sk.gettime() + return t2 - t1 +end + +return _M diff --git a/t/plugin/inspect.t b/t/plugin/inspect.t new file mode 100644 index 000000000000..e938431b4dff --- /dev/null +++ b/t/plugin/inspect.t @@ -0,0 +1,499 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +log_level('warn'); +repeat_each(1); +no_long_string(); +no_root_location(); +no_shuffle(); + +add_block_preprocessor(sub { + my ($block) = @_; + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } + + my $user_yaml_config = <<_EOC_; +plugin_attr: + inspect: + delay: 1 + hooks_file: "/tmp/apisix_inspect_hooks.lua" +_EOC_ + $block->set_value("yaml_config", $user_yaml_config); + + my $extra_init_worker_by_lua = $block->extra_init_worker_by_lua // ""; + $extra_init_worker_by_lua .= <<_EOC_; +local function gen_funcs_invoke(...) + local code = "" + for _, func in ipairs({...}) do + code = code .. "test." .. func .. "();" + end + return code +end +function set_test_route(...) + func = func or 'run1' + local t = require("lib.test_admin").test + local code = [[{ + "methods": ["GET"], + "uri": "/inspect", + "plugins": { + "serverless-pre-function": { + "phase": "rewrite", + "functions" : ["return function() local test = require(\\"lib.test_inspect\\");]] + .. gen_funcs_invoke(...) + .. [[ngx.say(\\"ok\\"); end"] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]] + return t('/apisix/admin/routes/inspect', ngx.HTTP_PUT, code) +end + +function do_request() + local http = require "resty.http" + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/inspect" + + local httpc = http.new() + local res = httpc:request_uri(uri, {method = "GET"}) + assert(res.body == "ok\\n") +end + +function write_hooks(code, file) + local file = io.open(file or "/tmp/apisix_inspect_hooks.lua", "w") + file:write(code) + file:close() +end +_EOC_ + $block->set_value("extra_init_worker_by_lua", $extra_init_worker_by_lua); + + # note that it's different from APISIX.pm, + # here we enable no_error_log ignoreless of error_log. + if (!$block->no_error_log) { + $block->set_value("no_error_log", "[error]"); + } + + if (!$block->timeout) { + $block->set_value("timeout", "10"); + } +}); + +add_cleanup_handler(sub { + unlink("/tmp/apisix_inspect_hooks.lua"); +}); + +run_tests; + +__DATA__ + +=== TEST 1: simple hook +--- config + location /t { + content_by_lua_block { + local code = set_test_route("run1") + if code >= 300 then + ngx.status = code + return + end + + write_hooks([[ + local dbg = require "apisix.inspect.dbg" + dbg.set_hook("t/lib/test_inspect.lua", 27, nil, function(info) + ngx.log(ngx.WARN, "var1=", info.vals.var1) + return true + end) + ]]) + + ngx.sleep(1.5) + + do_request() + + os.remove("/tmp/apisix_inspect_hooks.lua") + } + } +--- error_log +var1=hello + + + +=== TEST 2: filename only +--- config + location /t { + content_by_lua_block { + local code = set_test_route("run1") + if code >= 300 then + ngx.status = code + return + end + + write_hooks([[ + local dbg = require "apisix.inspect.dbg" + dbg.set_hook("test_inspect.lua", 27, nil, function(info) + ngx.log(ngx.WARN, "var1=", info.vals.var1) + return true + end) + ]]) + + ngx.sleep(1.5) + + do_request() + + os.remove("/tmp/apisix_inspect_hooks.lua") + } + } +--- error_log +var1=hello + + + +=== TEST 3: hook lifetime +--- config + location /t { + content_by_lua_block { + local code = set_test_route("run1") + if code >= 300 then + ngx.status = code + return + end + + write_hooks([[ + local dbg = require "apisix.inspect.dbg" + local hook1_times = 2 + dbg.set_hook("test_inspect.lua", 27, nil, function(info) + ngx.log(ngx.WARN, "var1=", info.vals.var1) + hook1_times = hook1_times - 1 + return hook1_times == 0 + end) + ]]) + + ngx.sleep(1.5) + + -- request 3 times, but hook triggered 2 times + for _ = 1,3 do + do_request() + end + + os.remove("/tmp/apisix_inspect_hooks.lua") + } + } +--- error_log +var1=hello +var1=hello + + + +=== TEST 4: multiple hooks +--- config + location /t { + content_by_lua_block { + local code = set_test_route("run1") + if code >= 300 then + ngx.status = code + return + end + + write_hooks([[ + local dbg = require "apisix.inspect.dbg" + dbg.set_hook("test_inspect.lua", 26, nil, function(info) + ngx.log(ngx.WARN, "var1=", info.vals.var1) + return true + end) + + dbg.set_hook("test_inspect.lua", 27, nil, function(info) + ngx.log(ngx.WARN, "var2=", info.vals.var2) + return true + end) + ]]) + + ngx.sleep(1.5) + + do_request() + + -- note that we don't remove the hook file, + -- used for next test case + } + } +--- error_log +var1=hello +var2=world + + + +=== TEST 5: hook file not removed, re-enabled by next startup +--- config + location /t { + content_by_lua_block { + local code = set_test_route("run1") + if code >= 300 then + ngx.status = code + return + end + + do_request() + + os.remove("/tmp/apisix_inspect_hooks.lua") + } + } +--- error_log +var1=hello + + + +=== TEST 6: soft link +--- config + location /t { + content_by_lua_block { + local code = set_test_route("run1") + if code >= 300 then + ngx.status = code + return + end + + write_hooks([[ + local dbg = require "apisix.inspect.dbg" + dbg.set_hook("t/lib/test_inspect.lua", 27, nil, function(info) + ngx.log(ngx.WARN, "var1=", info.vals.var1) + return true + end) + ]], "/tmp/test_real_tmp_file.lua") + + os.execute("ln -sf /tmp/test_real_tmp_file.lua /tmp/apisix_inspect_hooks.lua") + + ngx.sleep(1.5) + + do_request() + + os.remove("/tmp/apisix_inspect_hooks.lua") + os.remove("/tmp/test_real_tmp_file.lua") + } + } +--- error_log +var1=hello + + + +=== TEST 7: remove soft link would disable hooks +--- config + location /t { + content_by_lua_block { + local code = set_test_route("run1") + if code >= 300 then + ngx.status = code + return + end + + write_hooks([[ + local dbg = require "apisix.inspect.dbg" + dbg.set_hook("t/lib/test_inspect.lua", 27, nil, function(info) + ngx.log(ngx.WARN, "var1=", info.vals.var1) + return true + end) + ]], "/tmp/test_real_tmp_file.lua") + + os.execute("ln -sf /tmp/test_real_tmp_file.lua /tmp/apisix_inspect_hooks.lua") + + ngx.sleep(1.5) + os.remove("/tmp/apisix_inspect_hooks.lua") + ngx.sleep(1.5) + + do_request() + + os.remove("/tmp/test_real_tmp_file.lua") + } + } +--- no_error_log +var1=hello + + + +=== TEST 8: ensure we see all local variables till the hook line +--- config + location /t { + content_by_lua_block { + local code = set_test_route("run1") + if code >= 300 then + ngx.status = code + return + end + + write_hooks([[ + local dbg = require "apisix.inspect.dbg" + dbg.set_hook("t/lib/test_inspect.lua", 27, nil, function(info) + local count = 0 + for k,v in pairs(info.vals) do + count = count + 1 + end + ngx.log(ngx.WARN, "count=", count) + return true + end) + ]]) + + ngx.sleep(1.5) + + do_request() + + os.remove("/tmp/apisix_inspect_hooks.lua") + } + } +--- error_log +count=2 + + + +=== TEST 9: check upvalue of run2(), only upvalue used in function code are visible +--- config + location /t { + content_by_lua_block { + local code = set_test_route("run2") + if code >= 300 then + ngx.status = code + return + end + + write_hooks([[ + local dbg = require "apisix.inspect.dbg" + dbg.set_hook("t/lib/test_inspect.lua", 33, nil, function(info) + ngx.log(ngx.WARN, "upvar1=", info.uv.upvar1) + ngx.log(ngx.WARN, "upvar2=", info.uv.upvar2) + return true + end) + ]]) + + ngx.sleep(1.5) + + do_request() + + os.remove("/tmp/apisix_inspect_hooks.lua") + } + } +--- error_log +upvar1=2 +upvar2=nil + + + +=== TEST 10: check upvalue of run3(), now both upvar1 and upvar2 are visible +--- config + location /t { + content_by_lua_block { + local code = set_test_route("run3") + if code >= 300 then + ngx.status = code + return + end + + write_hooks([[ + local dbg = require "apisix.inspect.dbg" + dbg.set_hook("t/lib/test_inspect.lua", 37, nil, function(info) + ngx.log(ngx.WARN, "upvar1=", info.uv.upvar1) + ngx.log(ngx.WARN, "upvar2=", info.uv.upvar2) + return true + end) + ]]) + + ngx.sleep(1.5) + + do_request() + + os.remove("/tmp/apisix_inspect_hooks.lua") + } + } +--- error_log +upvar1=2 +upvar2=yes + + + +=== TEST 11: flush specific JIT cache +--- config + location /t { + content_by_lua_block { + local test = require("lib.test_inspect") + + local t1 = test.hot1() + local t8 = test.hot2() + + write_hooks([[ + local test = require("lib.test_inspect") + local dbg = require "apisix.inspect.dbg" + dbg.set_hook("t/lib/test_inspect.lua", 47, test.hot1, function(info) + return false + end) + ]]) + + ngx.sleep(1.5) + + local t2 = test.hot1() + local t9 = test.hot2() + + assert(t2-t1 > t1, "hot1 consumes at least double times than before") + assert(t9-t8 < t8*0.8, "hot2 not affected") + + os.remove("/tmp/apisix_inspect_hooks.lua") + + ngx.sleep(1.5) + + local t3 = test.hot1() + local t4 = test.hot2() + assert(t3-t1 < t1*0.8, "hot1 jit recover") + assert(t4-t8 < t4*0.8, "hot2 jit recover") + } + } + + + +=== TEST 12: flush the whole JIT cache +--- config + location /t { + content_by_lua_block { + local test = require("lib.test_inspect") + + local t1 = test.hot1() + local t8 = test.hot2() + + write_hooks([[ + local test = require("lib.test_inspect") + local dbg = require "apisix.inspect.dbg" + dbg.set_hook("t/lib/test_inspect.lua", 47, nil, function(info) + return false + end) + ]]) + + ngx.sleep(1.5) + + local t2 = test.hot1() + local t9 = test.hot2() + + assert(t2-t1 > t1, "hot1 consumes at least double times than before") + assert(t9-t8 > t8, "hot2 consumes at least double times than before") + + os.remove("/tmp/apisix_inspect_hooks.lua") + + ngx.sleep(1.5) + + local t3 = test.hot1() + local t4 = test.hot2() + assert(t3-t1 < t1*0.8, "hot1 jit recover") + assert(t4-t8 < t4*0.8, "hot2 jit recover") + } + }