The Grafana SimpleJson Advanced Endpoint
The following script acts as the Advanced Endpoint for requests coming from Grafana:
-- grafana-simple-json.lua
-- Lua library to use Grafana Simple JSON datasource to extract data via inmation WebAPI data
-- by using an advanced endpoint
--
-- https://grafana.com/grafana/plugins/grafana-simple-json-datasource
-- https://inmation.com/docs/api/latest/webapi/advancedendpoints.html
local lib = {}
local JS = require 'rapidjson'
lib.TOPIC = "grafana-simple-json"
lib.DEBUG = nil
function lib:_log_debug(header, detail)
if (self.DEBUG) then
inmation.log(4,("%s %s"):format(self.TOPIC, header),detail)
end
end
function lib:_parseQueryTarget(queryTarget)
local urlAndParams = {}
for token in string.gmatch(queryTarget, "[^?]+") do
table.insert(urlAndParams, token)
end
assert(#urlAndParams > 0)
local path = nil
local params = {}
-- can be 1 or multiple (KvP)
if #urlAndParams == 1 then
path = urlAndParams[1]
elseif #urlAndParams == 2 then
path = urlAndParams[1]
local strKvP = urlAndParams[2]
for kvpToken in string.gmatch(strKvP, "[^&]+") do
local kvpParts = {}
for s in string.gmatch(kvpToken, "[^=]+") do
table.insert(kvpParts, s)
end
if #kvpParts == 2 then
params[kvpParts[1]] = kvpParts[2]
end
end
else
error("Failed to get target path from query parameters or invalid length")
end
assert(type(path)=="string" and #path>0)
return path, params
end
function lib:search(arg, _, hlp)
self:_log_debug("request: search", JS.encode(arg))
-- request should be: { target: 'upper_50' }
-- response: [ { "text" :"upper_25", "value": 1}, { "text" :"upper_75", "value": 2} ]
local tags = inmation.findobjects(inmation.getsystempath(), 1, true, true)
local result = {}
for _,o in pairs(tags) do table.insert(result, o:path()) end
return hlp:createResponse(result, nil, 200)
end
function lib:query(arg, _, hlp)
self:_log_debug("request: query", JS.encode(arg))
local from = arg.range.from
local to = arg.range.to
local from_ms = inmation.gettime(from)
assert(type(from_ms)=="number", "from_ms not a number")
local to_ms = inmation.gettime(to)
assert(type(to_ms)=="number", "to_ms not a number")
assert((to_ms >= from_ms), "to is larger than from")
local paths = {}
local aggregates = {}
for _, o in pairs(arg.targets) do
assert((type(o.target)=="string" and #o.target>0), "target is empty")
local path, param = self:_parseQueryTarget(o.target)
if (type(param.aggregate)=="string" and #param.aggregate>0) then
table.insert(aggregates, param.aggregate)
else
table.insert(aggregates, "AGG_TYPE_TIMEAVERAGE")
end
table.insert(paths, path)
end
local interval_ms = arg.intervalMs
assert(type(interval_ms)=="number", "intervalMs not a number")
local interval_no = math.floor( ((to_ms - from_ms) / interval_ms) )
local res = inmation.gethistory(paths, from_ms, to_ms, interval_no, aggregates)
local result = {}
for i = 1, #paths do
local path = paths[i]
-- use only the objectname for the result, not the full path
local _, name = inmation.splitpath(path)
local target = name
-- if a alias exists, use this instead
local ok, obj = pcall(function() return inmation.getobject(path) end)
if ok then
local alias = obj.DisplayAlias
if (alias ~= nil and type(alias)=="string" and alias:len() > 0) then
target = alias
end
end
result[i] = { target = target, datapoints = {}}
end
for i = 1, interval_no do
for p = 1, #paths do
local vq = {}
table.insert(vq, res[p][i].V)
table.insert(vq, res[p][i].T)
local pathResult = result[p]
table.insert(pathResult.datapoints, vq)
end
end
return hlp:createResponse(result, nil, 200)
end
return lib