fuel-ccp-stacklight/docker/hindsight/modules/afd.lua

182 lines
4.6 KiB
Lua

-- Copyright 2015-2016 Mirantis, Inc.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
local cjson = require 'cjson'
local string = require 'string'
local table = require 'table'
local utils = require 'stacklight.utils'
local constants = require 'stacklight.constants'
local read_message = read_message
local assert = assert
local ipairs = ipairs
local pcall = pcall
local M = {}
setfenv(1, M) -- Remove external access to contain everything in the module
local function read_field(msg, name)
return msg.Fields[name]
end
function read_status(msg)
return read_field(msg, 'value')
end
function read_source(msg)
return read_field(msg, 'source')
end
function read_hostname(msg)
return read_field(msg, 'hostname')
end
function extract_alarms(msg)
local ok, payload = pcall(cjson.decode, msg.Payload)
if not ok or not payload.alarms then
return nil
end
return payload.alarms
end
-- return a human-readable message from an alarm table
-- for instance: "CPU load too high (WARNING, rule='last(load_midterm)>=5', current=7)"
function get_alarm_for_human(alarm)
local metric
if #(alarm.fields) > 0 then
local fields = {}
for _, field in ipairs(alarm.fields) do
fields[#fields+1] = field.name .. '="' .. field.value .. '"'
end
metric = string.format('%s[%s]', alarm.metric, table.concat(fields, ','))
else
metric = alarm.metric
end
local host = ''
if alarm.hostname then
host = string.format(', host=%s', alarm.hostname)
end
return string.format(
"%s (%s, rule='%s(%s)%s%s', current=%.2f%s)",
alarm.message,
alarm.severity,
alarm['function'],
metric,
alarm.operator,
alarm.threshold,
alarm.value,
host
)
end
function alarms_for_human(alarms)
local alarm_messages = {}
local hint_messages = {}
for _, v in ipairs(alarms) do
if v.tags and v.tags.dependency_level and v.tags.dependency_level == 'hint' then
hint_messages[#hint_messages+1] = get_alarm_for_human(v)
else
alarm_messages[#alarm_messages+1] = get_alarm_for_human(v)
end
end
if #hint_messages > 0 then
alarm_messages[#alarm_messages+1] = "Other related alarms:"
end
for _, v in ipairs(hint_messages) do
alarm_messages[#alarm_messages+1] = v
end
return alarm_messages
end
local alarms = {}
-- append an alarm to the list of pending alarms
-- the list is sent when inject_afd_metric is called
function add_to_alarms(status, fn, metric, fields, tags, operator, value, threshold, window, periods, message)
local severity = constants.status_label(status)
assert(severity)
alarms[#alarms+1] = {
severity=severity,
['function']=fn,
metric=metric,
fields=fields or {},
tags=tags or {},
operator=operator,
value=value,
threshold=threshold,
window=window or 0,
periods=periods or 0,
message=message
}
end
function get_alarms()
return alarms
end
function reset_alarms()
alarms = {}
end
-- inject an AFD event into the Heka pipeline
function inject_afd_metric(msg_type, msg_tag_name, msg_tag_value, metric_name,
value, hostname, source)
local payload
if #alarms > 0 then
payload = utils.safe_json_encode({alarms=alarms})
reset_alarms()
if not payload then
return
end
else
-- because cjson encodes empty tables as objects instead of arrays
payload = '{"alarms":[]}'
end
local msg = {
Type = msg_type,
Payload = payload,
Fields = {
name = metric_name,
value = value,
hostname = hostname,
source = source,
dimensions = {msg_tag_name, 'hostname', 'source'},
}
}
msg.Fields[msg_tag_name] = msg_tag_value
local err_code, err_msg = utils.safe_inject_message(msg)
if err_code ~= 0 then
return nil, err_msg
end
return msg
end
MATCH = 1
NO_MATCH = 2
NO_DATA = 3
MISSING_DATA = 4
return M