182 lines
4.6 KiB
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
|