--- Node-level utilities. -- -- @module kong.node local ffi = require "ffi" local private_node = require "kong.pdk.private.node" local uuid = require("kong.tools.uuid").uuid local bytes_to_str = require("kong.tools.string").bytes_to_str local floor = math.floor local lower = string.lower local match = string.match local gsub = string.gsub local sort = table.sort local insert = table.insert local ngx = ngx local shared = ngx.shared local C = ffi.C local ffi_new = ffi.new local ffi_str = ffi.string local NODE_ID_KEY = "kong:node_id" local node_id local shms = {} local n_workers = ngx.worker.count() ffi.cdef[[ int gethostname(char *name, size_t len); ]] for shm_name, shm in pairs(shared) do insert(shms, { zone = shm, name = shm_name, capacity = shm:capacity(), }) end local function convert_bytes(bytes, unit, scale) if not unit or lower(unit) == "b" then return floor(bytes) end return bytes_to_str(bytes, unit, scale) end local function sort_pid_asc(a, b) return a.pid < b.pid end local function new(self) local _NODE = { hostname = nil, } --- -- Returns the ID used by this node to describe itself. -- -- @function kong.node.get_id -- @treturn string The v4 UUID used by this node as its ID. -- @usage -- local id = kong.node.get_id() function _NODE.get_id() if node_id then return node_id end local shm = ngx.shared.kong local ok, err = shm:safe_add(NODE_ID_KEY, uuid()) if not ok and err ~= "exists" then error("failed to set 'node_id' in shm: " .. err) end node_id, err = shm:get(NODE_ID_KEY) if err then error("failed to get 'node_id' in shm: " .. err) end if not node_id then error("no 'node_id' set in shm") end return node_id end --- -- Returns memory usage statistics about this node. -- -- @function kong.node.get_memory_stats -- @tparam[opt] string unit The unit that memory is reported in. Can be -- any of `b/B`, `k/K`, `m/M`, or `g/G` for bytes, kibibytes, mebibytes, -- or gibibytes, respectively. Defaults to `b` (bytes). -- @tparam[opt] number scale The number of digits to the right of the decimal -- point. Defaults to 2. -- @treturn table A table containing memory usage statistics for this node. -- If `unit` is `b/B` (the default), reported values are Lua numbers. -- Otherwise, reported values are strings with the unit as a suffix. -- @usage -- local res = kong.node.get_memory_stats() -- -- res will have the following structure: -- { -- lua_shared_dicts = { -- kong = { -- allocated_slabs = 12288, -- capacity = 24576 -- }, -- kong_db_cache = { -- allocated_slabs = 12288, -- capacity = 12288 -- } -- }, -- workers_lua_vms = { -- { -- http_allocated_gc = 1102, -- pid = 18004 -- }, -- { -- http_allocated_gc = 1102, -- pid = 18005 -- } -- } -- } -- -- local res = kong.node.get_memory_stats("k", 1) -- -- res will have the following structure: -- { -- lua_shared_dicts = { -- kong = { -- allocated_slabs = "12.0 KiB", -- capacity = "24.0 KiB", -- }, -- kong_db_cache = { -- allocated_slabs = "12.0 KiB", -- capacity = "12.0 KiB", -- } -- }, -- workers_lua_vms = { -- { -- http_allocated_gc = "1.1 KiB", -- pid = 18004 -- }, -- { -- http_allocated_gc = "1.1 KiB", -- pid = 18005 -- } -- } -- } function _NODE.get_memory_stats(unit, scale) -- validate arguments do unit = unit or "b" scale = scale or 2 local pok, perr = pcall(bytes_to_str, 0, unit, scale) if not pok then error(perr, 2) end end local res = { workers_lua_vms = self.table.new(n_workers, 0), lua_shared_dicts = self.table.new(0, #shms), } -- get workers Lua VM allocated memory do if not shared.kong then goto lua_shared_dicts end local keys, err = shared.kong:get_keys() if not keys then res.workers_lua_vms.err = "could not get kong shm keys: " .. err goto lua_shared_dicts end if #keys == 1024 then -- Preventive warning log for future Kong developers, in case 'kong' -- shm becomes mis-used or over-used. ngx.log(ngx.WARN, "ngx.shared.kong:get_keys() returned 1024 keys, ", "but it may have more") end for i = 1, #keys do local pid = match(keys[i], "kong:mem:(%d+)") if not pid then goto continue end local w = self.table.new(0, 2) w.pid = tonumber(pid) local count, err = shared.kong:get("kong:mem:" .. pid) if err then w.err = "could not get worker's HTTP Lua VM memory (pid: " .. pid .. "): " .. err elseif type(count) ~= "number" then w.err = "could not get worker's HTTP Lua VM memory (pid: " .. pid .. "): reported value is corrupted" else count = count * 1024 -- reported value is in kb w.http_allocated_gc = convert_bytes(count, unit, scale) end insert(res.workers_lua_vms, w) ::continue:: end sort(res.workers_lua_vms, sort_pid_asc) end -- get lua_shared_dicts allocated slabs ::lua_shared_dicts:: for _, shm in ipairs(shms) do local allocated = shm.capacity - shm.zone:free_space() res.lua_shared_dicts[shm.name] = { capacity = convert_bytes(shm.capacity, unit, scale), allocated_slabs = convert_bytes(allocated, unit, scale), } end return res end --- -- Returns the name used by the local machine. -- -- @function kong.node.get_hostname -- @treturn string The local machine hostname. -- @usage -- local hostname = kong.node.get_hostname() function _NODE.get_hostname() if not _NODE.hostname then local SIZE = 253 -- max number of chars for a hostname local buf = ffi_new("unsigned char[?]", SIZE) local res = C.gethostname(buf, SIZE) if res ~= 0 then -- Return an empty string "" instead of nil and error message, -- because strerror is not thread-safe and the behavior of strerror_r -- is inconsistent across different systems. return "" end _NODE.hostname = gsub(ffi_str(buf, SIZE), "%z+$", "") end return _NODE.hostname end -- the PDK can be even when there is no configuration (for docs/tests) -- so execute below block only when running under correct context local prefix = self and self.configuration and self.configuration.prefix if prefix then -- precedence order: -- 1. user provided node id local configuration_node_id = self and self.configuration and self.configuration.node_id if configuration_node_id then ngx.log(ngx.WARN, "Manually specifying a `node_id` via configuration is deprecated as of 3.9 and will be removed in the 4.x.\n", "We strongly recommend avoiding this practice.\n", "Please note that if specified manually it must be unique across the cluster to ensure proper functionality.") node_id = configuration_node_id end -- 2. node id (if any) on file-system if not node_id then if prefix and self.configuration.role == "data_plane" then local id, err = private_node.load_node_id(prefix) if id then node_id = id ngx.log(ngx.DEBUG, "restored node_id from the filesystem: ", node_id) else ngx.log(ngx.WARN, "failed to restore node_id from the filesystem: ", err, ", a new node_id will be generated") end end end -- 3. generate a new id if not node_id then node_id = _NODE.get_id() end if node_id then ngx.log(ngx.INFO, "kong node-id: ", node_id) end end return _NODE end return { new = new, }