mirror of
https://github.com/ntop/ntopng.git
synced 2026-10-24 23:40:24 +00:00
382 lines
12 KiB
Lua
382 lines
12 KiB
Lua
--
|
|
-- (C) 2014-26 - ntop.org
|
|
--
|
|
|
|
local top_talkers_utils = {}
|
|
top_talkers_utils.MAX_NUM_ENTRIES = 10
|
|
top_talkers_utils.THRESHOLD_LOW = .05
|
|
top_talkers_utils.TOP_ENABLED_KEY = "ntopng.prefs.ifid_%i.interface_top_talkers_creation"
|
|
|
|
local OTHER_KEY = "Other"
|
|
local HIDDEN_KEY = "Hidden Hosts"
|
|
|
|
-- ########################################################
|
|
|
|
local function getTopEnabledKey(ifid)
|
|
return string.format(top_talkers_utils.TOP_ENABLED_KEY, ifid)
|
|
end
|
|
|
|
-- ########################################################
|
|
|
|
-- Per-direction bounded top: the array part holds at most MAX_NUM_ENTRIES
|
|
-- entries sorted by value (descending), 'sum' is the total of all the deltas.
|
|
-- Memory is O(MAX_NUM_ENTRIES) per VLAN instead of O(num hosts) and no full sort
|
|
-- is needed: most hosts are rejected with a single comparison.
|
|
local function newDirectionTop()
|
|
return {
|
|
n = 0,
|
|
sum = 0
|
|
}
|
|
end
|
|
|
|
-- ########################################################
|
|
|
|
local function topInsert(top, value, hostkey, hoststats)
|
|
local max_entries = top_talkers_utils.MAX_NUM_ENTRIES
|
|
local n = top.n
|
|
|
|
if n < max_entries then
|
|
n = n + 1
|
|
top.n = n
|
|
elseif value <= top[n].value then
|
|
return -- Smaller than the current smallest top entry
|
|
end
|
|
|
|
-- Insertion sort step: shift smaller entries down (the last one is dropped when full)
|
|
-- Basically what happens is the following, being the table ordered by the greater to the lowest
|
|
-- e.g. : 7 - 5 - 3 - 1
|
|
-- this algorithm starts from the end (latest position) and shift on the "right" the values
|
|
-- until it finds a greater value where it stops shifting
|
|
local i = n
|
|
while (i > 1) and (top[i - 1].value < value) do
|
|
top[i] = top[i - 1]
|
|
i = i - 1
|
|
end
|
|
|
|
top[i] = {
|
|
value = value,
|
|
hostkey = hostkey,
|
|
host = hoststats
|
|
}
|
|
end
|
|
|
|
-- ########################################################
|
|
|
|
local function makeHostRecord(entry)
|
|
local hoststats = entry.host
|
|
|
|
if hoststats == nil then
|
|
-- Pseudo entry (e.g. hidden hosts)
|
|
return {
|
|
address = entry.hostkey,
|
|
value = entry.value,
|
|
["local"] = "false"
|
|
}
|
|
end
|
|
|
|
local hostkey = entry.hostkey
|
|
local tskey = hoststats.tskey or hostkey
|
|
local name = hoststats.name
|
|
local rec = {
|
|
address = tskey,
|
|
value = entry.value,
|
|
["local"] = hoststats.localhost and "true" or "false"
|
|
}
|
|
|
|
if (name ~= nil) and (name ~= tskey) then
|
|
rec.label = name
|
|
end
|
|
|
|
if hostkey ~= tskey then
|
|
-- storing the IP address is necessary for MAC based keys to create the
|
|
-- correct URL
|
|
rec.ipaddr = hostkey
|
|
end
|
|
|
|
return rec
|
|
end
|
|
|
|
-- ########################################################
|
|
|
|
-- Converts a direction top into the list of records saved on the DB:
|
|
-- the first MAX_NUM_ENTRIES / 2 entries are always kept, the following ones
|
|
-- only if they are at least THRESHOLD_LOW of the total; the rest goes into "Other"
|
|
local function finalizeDirection(top, hidden_sum)
|
|
if hidden_sum > 0 then
|
|
topInsert(top, hidden_sum, HIDDEN_KEY, nil)
|
|
end
|
|
|
|
local total = top.sum
|
|
local min_kept = top_talkers_utils.MAX_NUM_ENTRIES / 2
|
|
local min_value = total * top_talkers_utils.THRESHOLD_LOW
|
|
local list = {}
|
|
local kept_sum = 0
|
|
|
|
for i = 1, top.n do
|
|
local entry = top[i]
|
|
|
|
-- Entries are sorted, once one is discarded all the following ones are too
|
|
if (i > min_kept) and (entry.value < min_value) then
|
|
break
|
|
end
|
|
|
|
list[i] = makeHostRecord(entry)
|
|
kept_sum = kept_sum + entry.value
|
|
end
|
|
|
|
local other = total - kept_sum
|
|
if other > 0 then
|
|
list[#list + 1] = {
|
|
address = OTHER_KEY,
|
|
value = other,
|
|
["local"] = "false"
|
|
}
|
|
end
|
|
|
|
return list
|
|
end
|
|
|
|
-- ########################################################
|
|
|
|
function top_talkers_utils.areTopEnabled(ifid)
|
|
local k = getTopEnabledKey(ifid)
|
|
|
|
return not (ntop.getPref(k) == "false")
|
|
end
|
|
|
|
-- ########################################################
|
|
|
|
function top_talkers_utils.disableTop(ifid)
|
|
local k = getTopEnabledKey(ifid)
|
|
ntop.setPref(k, "false")
|
|
end
|
|
|
|
-- ########################################################
|
|
|
|
function top_talkers_utils.enableTop(ifid)
|
|
local k = getTopEnabledKey(ifid)
|
|
ntop.delCache(k)
|
|
end
|
|
|
|
-- ########################################################
|
|
|
|
-- Computes the top talkers of the last minute (since the previous checkpoint)
|
|
-- in a single pass over the hosts, keeping only the top MAX_NUM_ENTRIES per
|
|
-- VLAN and direction. Returns the JSON to be saved on the DB.
|
|
function top_talkers_utils.makeTopJson(_ifname, ifid)
|
|
require "ntop_utils"
|
|
|
|
if not top_talkers_utils.areTopEnabled(ifid) then
|
|
return nil
|
|
end
|
|
|
|
-- Include here otherwise it's a useless waste of memory
|
|
local json = require "dkjson"
|
|
|
|
local getBatchedHostsInfo = interface.getBatchedHostsInfo
|
|
local isDeadlineApproaching = ntop.isDeadlineApproaching
|
|
local isShuttingDown = ntop.isShuttingDown
|
|
local vlans = {}
|
|
local next_slot = 0
|
|
local num_hosts = 0
|
|
local in_time = true
|
|
|
|
interface.select(_ifname)
|
|
|
|
-- Iterate the hosts batches directly (no per-host closure/iterator), the
|
|
-- deadline is checked once per batch.
|
|
-- NOTE: hosts are checkpointed (delta reset) by the C core when fetched
|
|
repeat
|
|
if isShuttingDown() then
|
|
break
|
|
end
|
|
|
|
if isDeadlineApproaching() then
|
|
in_time = false
|
|
break
|
|
end
|
|
|
|
local batch = getBatchedHostsInfo(next_slot, false --[[ no details ]])
|
|
|
|
if batch == nil then
|
|
break
|
|
end
|
|
|
|
for hostkey, hoststats in pairs(batch.hosts or {}) do
|
|
local delta = hoststats.delta
|
|
|
|
if delta ~= nil then
|
|
local vlan_id = hoststats.vlan or 0
|
|
local v = vlans[vlan_id]
|
|
local sent = delta.sent or 0
|
|
local rcvd = delta.rcvd or 0
|
|
|
|
if v == nil then
|
|
v = {
|
|
sent = newDirectionTop(),
|
|
rcvd = newDirectionTop(),
|
|
hidden_sent = 0,
|
|
hidden_rcvd = 0
|
|
}
|
|
vlans[vlan_id] = v
|
|
end
|
|
|
|
if sent > 0 then
|
|
local top = v.sent
|
|
top.sum = top.sum + sent
|
|
|
|
if hoststats.hiddenFromTop then
|
|
v.hidden_sent = v.hidden_sent + sent
|
|
else
|
|
topInsert(top, sent, hostkey, hoststats)
|
|
end
|
|
end
|
|
|
|
if rcvd > 0 then
|
|
local top = v.rcvd
|
|
top.sum = top.sum + rcvd
|
|
|
|
if hoststats.hiddenFromTop then
|
|
v.hidden_rcvd = v.hidden_rcvd + rcvd
|
|
else
|
|
topInsert(top, rcvd, hostkey, hoststats)
|
|
end
|
|
end
|
|
end
|
|
|
|
num_hosts = num_hosts + 1
|
|
end
|
|
|
|
next_slot = batch.nextSlot
|
|
until (next_slot == nil) or (next_slot == 0)
|
|
|
|
if not in_time then
|
|
require "lua_trace"
|
|
|
|
local deadline = ntop.getDeadline()
|
|
local remaining = deadline - os.time()
|
|
|
|
if num_hosts == 0 then
|
|
-- Nothing processed: the script started (almost) at its deadline,
|
|
-- i.e. it was waiting in the periodic activities queue
|
|
traceError(TRACE_ERROR, TRACE_CONSOLE,
|
|
string.format("[%s] Top talkers not computed: the periodic script started too late " ..
|
|
"(%d sec before its deadline). Are the periodic activities overloaded?",
|
|
_ifname, remaining))
|
|
else
|
|
traceError(TRACE_ERROR, TRACE_CONSOLE,
|
|
string.format("[%s] Cannot complete top talkers generation in 1 minute " ..
|
|
"(processed %d hosts, %d sec before the deadline). Is there a huge number of hosts in the system?",
|
|
_ifname, num_hosts, remaining))
|
|
end
|
|
end
|
|
|
|
local res = {}
|
|
|
|
for vlan_id, v in pairs(vlans) do
|
|
local total = v.sent.sum + v.rcvd.sum
|
|
|
|
-- Skip VLANs without traffic in the last minute
|
|
if total > 0 then
|
|
res[#res + 1] = {
|
|
hosts = {{
|
|
senders = finalizeDirection(v.sent, v.hidden_sent),
|
|
receivers = finalizeDirection(v.rcvd, v.hidden_rcvd)
|
|
}},
|
|
value = total,
|
|
address = tostring(vlan_id)
|
|
}
|
|
end
|
|
end
|
|
|
|
return json.encode({
|
|
vlan = res
|
|
})
|
|
end
|
|
|
|
-- ########################################################
|
|
|
|
-- Computes label and url during visualization
|
|
function top_talkers_utils.enrichRecordInformation(class_key, rec, show_vlan)
|
|
require "ntop_utils"
|
|
require "label_utils"
|
|
|
|
local url = ""
|
|
local label = rec.label or rec.address
|
|
local url_target = rec.address
|
|
|
|
if (rec.address ~= "Other") and (rec.address ~= "Hidden Hosts") then
|
|
if class_key == "hosts" then
|
|
local address = rec.address
|
|
|
|
if ends(address, "_v4") or ends(address, "_v6") then
|
|
rec.visual_addr = visualTsKey(address)
|
|
address = string.sub(address, 1, string.len(address) - 3)
|
|
|
|
if (rec.ipaddr ~= nil) then
|
|
url_target = rec.ipaddr
|
|
end
|
|
end
|
|
|
|
url_target = url_target .. "&tskey=" .. rec.address
|
|
|
|
url = ntop.getHttpPrefix() .. '/lua/host_details.lua?always_show_hist=true&host='
|
|
|
|
-- Use the host alias as label, if set
|
|
local alt_name = ip2label(address, 0, 100)
|
|
|
|
if not isEmptyString(alt_name) and (alt_name ~= address) then
|
|
label = alt_name
|
|
else
|
|
local hinfo = hostkey2hostinfo(address)
|
|
if not show_vlan then
|
|
hinfo.vlan = 0
|
|
end
|
|
alt_name = hostinfo2label(hinfo)
|
|
|
|
if not isEmptyString(alt_name) and (alt_name ~= address) then
|
|
label = alt_name
|
|
else
|
|
label = address
|
|
end
|
|
end
|
|
elseif class_key == "asn" then
|
|
url = ntop.getHttpPrefix() .. '/lua/hosts_stats.lua?asn='
|
|
elseif class_key == "networks" then
|
|
url = ntop.getHttpPrefix() .. '/lua/hosts_stats.lua?network='
|
|
|
|
local network_name = nil
|
|
if rec.address == "-1" then
|
|
network_name = i18n("remote_networks")
|
|
else
|
|
network_name = getFullLocalNetworkName(ntop.getNetworkNameById(tonumber(rec.address)))
|
|
end
|
|
if not isEmptyString(network_name) then
|
|
label = network_name
|
|
end
|
|
elseif class_key:contains("os") then
|
|
url = ntop.getHttpPrefix() .. '/lua/hosts_stats.lua?os='
|
|
end
|
|
|
|
if not isEmptyString(url) then
|
|
url = url .. url_target
|
|
end
|
|
end
|
|
|
|
-- Update record information
|
|
rec.url = url
|
|
rec.label = label
|
|
end
|
|
|
|
-- ########################################################
|
|
|
|
function top_talkers_utils.enrichVlanInformation(vlan_tbl)
|
|
local vlan_id = ternary(vlan_tbl.address, tostring(vlan_tbl.address), "0")
|
|
vlan_tbl["label"] = vlan_id
|
|
vlan_tbl["name"] = vlan_id
|
|
vlan_tbl["url"] = ntop.getHttpPrefix() .. '/lua/hosts_stats.lua?vlan=' .. vlan_id
|
|
end
|
|
|
|
-- ########################################################
|
|
|
|
return top_talkers_utils
|