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