Files
2026-07-13 12:32:21 +08:00

314 lines
7.9 KiB
Lua

--- 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,
}