mirror of
https://github.com/ntop/ntopng.git
synced 2026-04-28 23:19:33 +00:00
248 lines
7.2 KiB
Lua
248 lines
7.2 KiB
Lua
|
|
-- TODO localize
|
|
local RRD_RESULT_ERROR_EMPTY = "Empty RRD"
|
|
local RRD_RESULT_ERROR_MALFORMED = "Malformed RRD"
|
|
|
|
SECONDS_IN_A_HOUR = 3600
|
|
SECONDS_IN_A_DAY = SECONDS_IN_A_HOUR*24
|
|
|
|
function timestamp_to_date(ts)
|
|
return os.date("*t", ts)
|
|
end
|
|
|
|
function date_to_timestamp(dt)
|
|
return os.time(dt)
|
|
end
|
|
|
|
function date_month_tostring(m)
|
|
return os.date("%B", date_to_timestamp(m))
|
|
end
|
|
|
|
function date_day_tostring(d)
|
|
local res = os.date("%d", date_to_timestamp(d))
|
|
if d.day < 10 then
|
|
res = string.sub(res, 2)
|
|
end
|
|
return res
|
|
end
|
|
|
|
function date_wday_tostring(d)
|
|
return os.date("%A", date_to_timestamp(d))
|
|
end
|
|
|
|
-- returns the days in the given year
|
|
function date_get_year_days(y)
|
|
return timestamp_to_date(os.time{year=y+1, day=1, month=1, hour=0} - 1).yday
|
|
end
|
|
|
|
function date_tostring(dt)
|
|
return os.date("%x %X", dt)
|
|
end
|
|
|
|
--
|
|
-- Fetches data from RRD derivate counters
|
|
--
|
|
-- Parameters
|
|
-- rrdname = path of the RRD
|
|
-- epoch_start = start timestamp for query
|
|
-- epoch_end = end timestamp for query
|
|
--
|
|
-- Returns
|
|
-- On error:
|
|
-- result.error = error string
|
|
-- On success:
|
|
-- result.data = a table, where each key is a data series containing a list of fetched values
|
|
-- result.count = number of values in each data series
|
|
-- result.start = timestamp for first value
|
|
-- result.step = timestamp step between values
|
|
-- result.names = rrd column names
|
|
--
|
|
function rrd_fetch_derivate(rrdname, epoch_start, epoch_end)
|
|
local result = {}
|
|
|
|
if(not ntop.notEmptyFile(rrdname)) then
|
|
result.error = RRD_RESULT_ERROR_EMPTY
|
|
else
|
|
local fstart, fstep, fnames, fdata = ntop.rrd_fetch(rrdname, 'AVERAGE', epoch_start, epoch_end)
|
|
if(fstart == nil) then
|
|
result.error = RRD_RESULT_ERROR_MALFORMED
|
|
else
|
|
result.start = fstart + fstep
|
|
result.step = fstep
|
|
result.data = {}
|
|
|
|
-- for the data series, use the same labels as the RRD counters
|
|
for j=1,#fnames do
|
|
result.data[fnames[j]] = {}
|
|
end
|
|
|
|
local get_rrd_value = function(x) if(x ~= x) or (x < 0) then return 0 else return x * fstep end end
|
|
|
|
for i=1,#fdata do
|
|
for j=1,#fnames do
|
|
table.insert(result.data[fnames[j]], get_rrd_value(fdata[i][j]))
|
|
end
|
|
end
|
|
|
|
result.count = #fdata
|
|
result.names = fnames
|
|
end
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
--
|
|
-- Transforms RRD data to meet the specified resolution.
|
|
--
|
|
-- Parameters
|
|
-- epoch_start - wanted epoch start
|
|
-- epoch_end - wanted end epoch
|
|
-- resolution - wanted resolution
|
|
-- traffic - input traffic with the format below, plus the "time" column
|
|
-- columns - a list of RRD column names
|
|
--
|
|
-- traffic format (compatible with rrd_fetch_derivate return value):
|
|
-- .start start RRD date
|
|
-- .step RRD hardware step
|
|
-- .count number of RRD rows
|
|
-- .data table containing [.count] rows of tables with [columns] format
|
|
--
|
|
-- Returns
|
|
-- On success:
|
|
-- result table, containing tables with [columns] format, with the specified resolution
|
|
--
|
|
-- On RRD error:
|
|
-- result
|
|
-- .error - with some error message
|
|
--
|
|
function rrd_fix_resolution(epoch_start, epoch_end, resolution, traffic, columns)
|
|
-- BEGIN parameters check
|
|
if (columns == nil or #columns < 1) then
|
|
error("No data columns")
|
|
end
|
|
epoch_start = tonumber(epoch_start)
|
|
if (epoch_start == nil or traffic == nil) then
|
|
error("Parameter error")
|
|
end
|
|
local rrd_start = tonumber(traffic.start)
|
|
local rrd_step = tonumber(traffic.step)
|
|
local rrd_count = tonumber(traffic.count)
|
|
local rrd_data = traffic.data
|
|
local target_resol = tonumber(resolution)
|
|
if (rrd_start == nil or rrd_step == nil or rrd_count == nil or rrd_data == nil or target_resol == nil) then
|
|
error("Parameter error")
|
|
end
|
|
-- END parameters check
|
|
|
|
-- check resolution consinstency
|
|
if traffic.step > resolution then
|
|
return {error=i18n('error_rrd_low_resolution', {prefs=ntop.getHttpPrefix().."/lua/admin/prefs.lua?subpage_active=on_disk_rrds"})}
|
|
end
|
|
|
|
-- functions to handle the n-dimensions counters
|
|
local function create_counters()
|
|
local counters = {}
|
|
for i=1,#columns do
|
|
counters[columns[i]] = 0
|
|
end
|
|
return counters
|
|
end
|
|
-- iterates the n-dimensions counters and calls the 1-dimension callback, passing current counter value and column
|
|
-- callback should return the new counter value
|
|
local function for_each_counter_do_update(counters, callback)
|
|
for i=1,#columns do
|
|
local col = columns[i]
|
|
counters[col] = callback(counters[col], col)
|
|
end
|
|
end
|
|
|
|
-- initialize
|
|
local integr_ctrs = create_counters()
|
|
local idx = 1
|
|
local time_col = "time"
|
|
local oldtime = epoch_start
|
|
local time_sum = epoch_start
|
|
local result = {[time_col]={}, hwstep=rrd_step}
|
|
for i=1,#columns do
|
|
result[columns[i]] = {}
|
|
end
|
|
|
|
-- special case
|
|
if rrd_start < epoch_start then
|
|
local toskip = math.max(math.ceil((epoch_start - rrd_start) / rrd_step), rrd_count)
|
|
old_time = rrd_start + rrd_step * (toskip-1)
|
|
local aligment = (1 - (epoch_start - old_time) / rrd_step)
|
|
|
|
-- skip starting rows
|
|
idx = toskip + 1
|
|
for_each_counter_do_update(integr_ctrs, function (value, col)
|
|
return value + traffic.data[col][toskip] * aligment
|
|
end)
|
|
end
|
|
|
|
-- precondition: rrd_start >= time_sum
|
|
local curtime = rrd_start + (idx-1) * rrd_step
|
|
local orig_resol = target_resol
|
|
|
|
while idx <= rrd_count and time_sum <= epoch_end do
|
|
-- handle daylight changes
|
|
target_resol = orig_resol
|
|
local from_dst = timestamp_to_date(time_sum).isdst
|
|
local to_dst = timestamp_to_date(curtime).isdst
|
|
if from_dst and not to_dst then
|
|
target_resol = target_resol + 3600
|
|
elseif not from_dst and to_dst then
|
|
-- 1 to avoid infinite loop
|
|
target_resol = math.max(target_resol - 3600, 1)
|
|
end
|
|
|
|
local tdiff = curtime - time_sum
|
|
|
|
if tdiff >= target_resol then
|
|
local prefix_t = time_sum + target_resol - oldtime
|
|
|
|
-- Calculate the traffic belonging to previous step
|
|
local prefix_slice = prefix_t / rrd_step
|
|
local prefix_ctrs = create_counters()
|
|
-- Set prefix_ctrs with the prefix_slice of traffic
|
|
for_each_counter_do_update(prefix_ctrs, function (value, col)
|
|
return traffic.data[col][idx] * prefix_slice
|
|
end)
|
|
|
|
-- Sum prefix slice of traffic and save result
|
|
table.insert(result[time_col], time_sum)
|
|
for_each_counter_do_update(integr_ctrs, function (value, col)
|
|
table.insert(result[col], value + prefix_ctrs[col])
|
|
-- set counters to the remaining slice
|
|
return traffic.data[col][idx] - prefix_ctrs[col]
|
|
end)
|
|
|
|
time_sum = time_sum + target_resol
|
|
tdiff = tdiff - prefix_t
|
|
else
|
|
-- Accumulate partial slices of traffic
|
|
for_each_counter_do_update(integr_ctrs, function (value, col)
|
|
return value + traffic.data[col][idx]
|
|
end)
|
|
end
|
|
|
|
oldtime = curtime
|
|
curtime = curtime + rrd_step
|
|
idx = idx + 1
|
|
end
|
|
|
|
-- case RRD end is before epoch_end
|
|
while time_sum <= epoch_end do
|
|
table.insert(result[time_col], time_sum)
|
|
-- Save integr_ctrs result
|
|
for_each_counter_do_update(integr_ctrs, function (value, col)
|
|
table.insert(result[col], value)
|
|
-- will zero integr_ctrs for next intervals
|
|
return 0
|
|
end)
|
|
time_sum = time_sum + target_resol
|
|
end
|
|
|
|
return result
|
|
end
|