二次开发指南

sysSentry作为一款巡检任务管理框架,除了管理已提供的插件外,也支持对用户开发的插件进行管理,想要开发一款新的插件并通过sysSentry框架进行管理,需要实现以下能力:

  1. 编写插件管理配置文件
  2. 完成插件功能开发

本文档将详细介绍如何使用sysSentry提供的对外接口开发新的插件。

插件管理配置文件

用户编写的新插件可以通过sysSentry进行管理,为了达到此目的,需要用户为新插件增加对应的配置文件,该文件应放置在/etc/sysSentry/tasks/目录下,文件名为[插件名].mod,插件名仅支持字母、数字和下划线。所有配置项需写在[common]段下。假设插件名为test,配置文件参考:

oneshot类型任务的配置示例:

shell
[root@openEuler ~]# cat /etc/sysSentry/tasks/test.mod
[common]
enabled=yes                         # 必选,是否加载插件
task_start=/usr/bin/test            # 必选,插件启动命令
task_stop=pkill -f /usr/bin/test    # 必选,插件停止命令
type=oneshot                        # 必选,插件任务类型
onstart=yes                         # 可选,插件随sysSentry服务启动时自动启动

period类型任务的配置示例:

shell
[root@openEuler ~]# cat /etc/sysSentry/tasks/test.mod
[common]
enabled=yes                         # 必选,是否加载插件
task_start=/usr/bin/test            # 必选,插件启动命令
task_stop=kill $pid                 # 必选,插件停止命令
type=period                         # 必选,插件任务类型
interval=10                         # 可选,周期执行间隔(仅period类型生效)
heartbeat_interval=120              # 可选,心跳检测间隔
onstart=yes                         # 可选,插件随sysSentry服务启动时自动启动

配置项说明

各配置项的详细说明如下:

配置项是否必选取值范围默认值(配置非法值时)说明
enabled必选"yes" 或 "no"无(缺失或非法时配置文件加载失败)控制是否加载该插件。设置为"yes"时加载并启用插件,设置为"no"时加载但禁用插件。缺失此配置项或取值非法时,整个配置文件加载失败
type必选"oneshot" 或 "period"无(缺失或非法时配置文件加载失败)插件任务类型。"oneshot"表示一次性任务,执行一次后退出;"period"表示周期性任务,按固定间隔反复执行。缺失此配置项或取值非法时,整个配置文件加载失败
task_start必选字符串(可执行命令路径)无(缺失时配置文件加载失败)插件启动命令,必须为可执行文件路径或命令。sysSentry通过subprocess.Popen执行该命令
task_stop必选字符串(停止命令)无(缺失时配置文件加载失败)插件停止命令。支持使用$pid作为占位符,运行时会被替换为插件进程的实际PID
task_pre可选字符串(命令)None(不执行前置命令)插件启动前执行的命令。多条命令以英文";"分隔,依次执行。任一命令执行失败(产生stderr输出)时,插件不会启动
task_post可选字符串(命令)None(不执行后置命令)插件停止后执行的命令。多条命令以英文";"分隔,依次执行。命令执行失败仅记录警告日志,不影响插件停止流程
interval可选(仅period类型生效)正整数(秒)3(使用inspect.conf中的全局配置值)周期执行间隔时间,单位为秒。仅period类型任务有效。缺失时使用/etc/sysSentry/inspect.conf中的全局Interval配置(默认3秒);配置为非整数或小于等于0时同样回退到全局默认值
注: interval并非严格遵守设置的时间,可能会长于预期时间。
heartbeat_interval可选正整数(秒),最小有效值为60-1(禁用心跳检测)心跳检测间隔时间,单位为秒。配置后插件需通过心跳socket发送心跳消息,超过该间隔未收到心跳则插件被标记为失败并发送SIGTERM信号终止。配置为非整数或小于等于0时禁用心跳检测(值为-1);配置值大于0但小于60时强制调整为60。
注: heartbeat_interval并非严格遵守设置的时间,可能会长于预期时间。
onstart可选"yes" 或 "no"oneshot类型:no(不随服务启动);period类型:yes(随服务启动)控制插件是否随sysSentry服务启动时自动启动。oneshot类型仅"yes"生效,其他值均视为不自动启动;period类型仅"no"不生效,其他值均视为自动启动
env_file可选字符串(文件路径)""(空字符串,不加载环境变量)插件环境变量文件路径。文件必须存在、为常规文件且可读,格式为KEY=VALUE(以#开头的行为注释,值可用双引号包裹)。路径不存在、非常规文件或不可读时,配置值回退为空字符串
conflict可选"up"、"down" 或 "kill""up"冲突检测策略,控制当task_start命令对应的进程已在运行时的处理方式。"up":不做冲突检查,直接启动(默认行为);"down":检测到冲突则拒绝启动;"kill":先终止已运行的进程再启动。缺失或取值非法时默认为"up"
alarm_id可选整数,1001~1128None(不启用告警功能)告警ID,用于关联xalarmd告警服务。取值必须在1001~1128范围内,已占用ID不可复用。缺失或取值非法时不启用告警功能
alarm_clear_time可选非负整数(秒)15(秒)告警老化清理时间,单位为秒。超过该时间的告警事件将被从告警列表中清除。仅在alarm_id有效时生效。缺失或取值非法(负数或非整数)时默认为15秒

说明:

  1. 缺失必选配置项(enabled、type、task_start、task_stop)或取值非法时,整个配置文件加载失败,插件不会被管理。
  2. type字段不支持动态修改,通过sentryctl reload重新加载配置时,如果type值发生变化则重新加载失败。
  3. task_pre命令失败会阻止插件启动;task_post命令失败仅记录警告日志,不会阻止插件停止流程。

插件功能开发

插件使用限制

用户开发的新插件需满足如下要求:

  1. 插件开发语言无限制,但建议用户优选python或c语言开发,目前sysSentry仅提供python和c语言的二次开发接口;
  2. 所有插件必须为可执行文件;
  3. 所有插件必须包含停止命令,执行该命令可准确的停止插件任务而不影响系统上其他程序的运行;

获取采集数据(可选)

如果用户希望通过sentryCollector采集服务获取系统数据,请参考对接sentryCollector采集服务章节。

插件事件告警上报

用户可通过告警上报接口将插件的告警信息上报到xalarmd服务,并通过get_alarm接口查看告警内容:

shell
[root@openEuler ~]# sentryctl get_alarm <插件名>

sysSentry提供python与c两种语言的对外接口。

告警上报使用限制

  1. 告警上报仅支持告警id的范围为1001-1128共128种,已使用告警ID见下表。

    常量描述
    MEMORY_ALARM_ID1001内存巡检告警ID
    SLOW_IO_ALARM_ID1002慢IO告警事件ID
    ALARM_REBOOT_EVENT1003BMC下电事件告警ID
    ALARM_REBOOT_ACK_EVENT1004BMC下电ack事件告警ID
    ALARM_OOM_EVENT1005OOM事件告警ID
    ALARM_OOM_ACK_EVENT1006OOM ack事件告警ID
    ALARM_PANIC_EVENT1007Panic事件告警ID
    ALARM_PANIC_ACK_EVENT1008Panic ack事件告警ID
    ALARM_KERNEL_REBOOT_EVENT1009内核重启事件告警ID
    ALARM_KERNEL_REBOOT_ACK_EVENT1010内核重启ack事件告警ID
    ALARM_UBUS_MEM_EVENT1013UBUS内存故障事件告警ID
    ALARM_LINK_EVENT1016链路(link)事件告警ID
    ALARM_RAS_SENTRY_EVENT1015BMC RAS告警事件告警ID
    SYSSENTRY_DOWN_ALARM_ID1128sysSentry服务停止告警告警ID

    上面告警ID已被插件占用,用户新增插件不可复用,并且1011,1012和1014也不可使用(预留)。

  2. 告警上报最大支持8191个字符。

python实现插件告警上报

需要安装pysentry_notify软件包:

shell
[root@openEuler ~]# yum install -y pysentry_notify

接口 告警信息上报

接口xalarm_report(alarm_id, alarm_level, alarm_type, puc_paras)
描述巡检插件可以通过该接口上报告警信息到xalarmd服务
参数alarm_id -- 告警id,整数类型
alarm_level -- 告警级别,枚举类型,取值为:MINOR_ALM(一般告警)、MAJOR_ALM(严重告警)和CRITICAL_ALM(致命告警)
alarm_type -- 告警类别,枚举类型,取值为:ALARM_TYPE_OCCUR(告警产生)和ALARM_TYPE_RECOVER(故障恢复)
punc_params -- 告警描述信息,字符串类型
限制1. 告警id限制取值范围为1001-1128。目前1001(内存巡检)、1002(慢IO检测)已被占用,不建议使用
2. 告警描述信息,最大长度为8191,在慢IO巡检中以json格式通信,具体json中各字段可以参考下面:慢IO上报json格式说明。
返回值若上报告警成功,则返回值为True,否则返回值为False。

慢IO上报json格式说明:

参数名称类型取值说明
device_name字符串发生慢IO事件的故障盘设备名,例如"/dev/sda"
reason字符串慢IO事件的故障原因,取值如下范围 disk_slow:可能由于盘侧响应码导致的慢IO;kernel_stack:可能由于内核栈导致的慢IO;high_press:可能由于业务压力大导致的慢IO
block_stack字符串列表存在异常的IO调用栈列表,例如["bio","rq_driver"],取值范围如下:bio、 throtl、 wbt、 gettag、plug、deadline、 hctx、requeue、rq_driver
io_type字符串出现慢IO的io类型,取值范围如下:read:读io出现慢io场景;write:写io出现慢io场景
alarm_source字符串告警来源插件,取值范围如下:avg_block_io:平均阈值检测插件告警;ai_block_io:ai阈值检测插件告警
alarm_type字符串出现慢IO告警的类型,取值范围如下:latency:时延数据超过阈值产生慢io告警;iodump:超时未完成的IO数量超过阈值产生慢IO告警
detailsJSON格式数据列表详细的IO时延数据清单

示例:

python
from xalarm.sentry_notify import (
    xalarm_report,
    MAJOR_ALM,
    ALARM_TYPE_OCCUR
)

ALARM_ID = 1002
ALARM_MSG = """
{
"alarm_info": {
            "alarm_source": "avg_block_io",
            "driver_name": "sda",
            "io_type": "write",
            "reason": "IO press",
            "block_stack": "bio,wbt",
            "alarm_type": "latency",
            "details": {
                "latency": "gettag: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], rq_driver: [0,0,0,0,0,437.9,0,0,0,0,517,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], bio: [0,0,0,0,0,521.1,0,0,0,0,557.8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], wbt: [0,0,0,0,0,0,8.5,0,0,0,0,12.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]",
                "iodump": "gettag: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], rq_driver: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], bio: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], wbt: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]"
            }
}
}
"""

if __name__ == "__main__":
    ret = xalarm_report(ALARM_ID, MAJOR_ALM, ALARM_TYPE_OCCUR, ALARM_MSG)
    if ret == -1:
        print("send failed.")

c语言实现插件告警上报

需要安装libxalarm软件包:

shell
[root@openEuler ~]# yum install -y libxalarm

开发环境还需要安装libxalarm-devel包(构建依赖,非运行依赖):

shell
[root@openEuler ~]# yum install -y libxalarm-devel
单向通信接口

单向通信接口,是向xalarmd服务上报事件告警的接口。

接口信息
接口int xalarm_Report(unsigned short usAlarmId, unsigned char ucAlarmLevel, unsigned char ucAlarmType, char *pucParas);
描述sysSentry告警上报接口,用于向xalarmd上报需要转发的告警
参数usAlarmId -- 告警id
usAlarmLevel -- 告警级别,枚举类型,取值为:MINOR_ALM(一般告警)、MAJOR_ALM(严重告警)或CRITICAL_ALM(致命告警)
ucAlarmType -- 告警类别,取值范围为ALARM_TYPE_OCCUR(告警产生)或ALARM_TYPE_RECOVER(故障恢复)
pucParas -- 告警描述信息,长度上限为8191个字符
限制1. 告警id限制取值范围为1001-1128。目前1001(内存巡检)、1002(慢IO检测)已被占用,不建议使用
2. 告警描述信息最大长度为8191
返回值返回0表示成功,失败返回-1
使用示例
shell
[root@openEuler ~]# cat send_alarm.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <xalarm/register_xalarm.h>

#define ALARMID 1002

int main(int argc, char **argv)
{
    int alarmId = ALARMID;
    int level = MAJOR_ALM;
    int type = ALARM_TYPE_OCCUR;
    unsigned char *msg = "{\""
                            "alarm_info\": {"
                                "\"alarm_source\": \"avg_block_io\","
                                "\"driver_name\": \"sda\","
                                "\"io_type\": \"write\","
                                "\"reason\": \"IO press\","
                                "\"block_stack\": \"bio,wbt\","
                                "\"alarm_type\": \"latency\","
                                "\"details\": {"
                                    "\"latency\": \"gettag: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], rq_driver: [0,0,0,0,0,437.9,0,0,0,0,517,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], bio: [0,0,0,0,0,521.1,0,0,0,0,557.8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], wbt: [0,0,0,0,0,0,8.5,0,0,0,0,12.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]\","
                                    "\"iodump\": \"gettag: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], rq_driver: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], bio: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], wbt: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]\""
                                    "}"
                                "}"
                            "}\0";

    int ret = xalarm_Report(alarmId, level, type, msg);
    
    if (ret == -1) {
        printf("send failed.\n");
    }

    return 0;
}
[root@openEuler ~]# gcc send_alarm.c -o send_alarm -lxalarm
双向通信接口

双向通信接口,是指既可以向xalarmd服务上报事件消息,又可以对消息进行回复。

接口信息
接口int xalarm_register_event(struct alarm_register** register_info, struct alarm_subscription_info id_filter);
描述用于向xalarmd服务创建socket连接,仅xalarm_get_event接口使用前需要注册,xalarm_report_event接口调用无需注册即可使用
参数register_info:订阅注册信息结构体指针地址
id_filter:订阅告警ID列表,为alarm_subscription_info结构体类型,可支持订阅1001-1128号告警,alarm_subscription_info结构体内有一个长度为128的数组id_filter,每个元素为int类型,表示需要订阅的id号,该结构体还有一个表示数组长度的成员len,设置长度为已需要订阅的告警id数量后才会生效。
返回值返回值为整数类型,0表示注册成功,若返回值小于0,可能的原因如下:
1. 返回值若为-ENOTCONN,表示与xalarmd进程建立连接失败,建议稍后重新注册。
2.返回值若为-EINVAL,则可能是register_info传入空指针,或者id_filter不符合要求。
3. 返回值若为-ENOMEM,则是申请内存空间失败。
接口void xalarm_unregister_event(struct alarm_register **register_info);
描述用于解除对xalarmd服务的告警订阅信息,释放socket连接
参数register_info:alarm_register结构体类型,用于保存与xalarmd服务通信的socket连接以及订阅的告警id信息
返回值无返回值,若解注册失败或者还未解注册程序异常退出,xalarmd服务也会定期清理无效的socket连接
接口int xalarm_report_event(unsigned short usAlarmId, char *pucParas, size_t len);
描述用户可以通过该接口上报告警信息到xalarmd服务
参数usAlarmId:告警id,整数类型。
pucParas:告警描述信息,字符串信息
len: 字符串pucParas的长度,不包含'\0'
约束告警id取值范围为1001-1128
告警描述信息最大长度为8191
返回值若上报告警成功,则返回值为0,否则返回值小于0.
1.返回值为-EINVAL,则传入参数告警id超过取值范围或者pucParas为NULL以及超过8191的长度限制。
2.返回值为-ENOTCONN,socket连接失败,建议重新发送消息。
3.返回值为-ECOMM,发送socket消息失败,建议重新发送消息。
4.返回值为-ENODEV,则为socket创建失败。
接口int xalarm_get_event(struct alarm_msg *msg, struct alarm_register *register_info);
描述用于接收register_info订阅过的告警信息,可多次调用,每次调用会阻塞程序直到收到告警信息才会停止阻塞
参数msg:告警消息指针地址,使用之前建议使用memset清空内容。
register_info:alarm_register结构体类型,用于保存与xalarmd通信的socket连接以及订阅的告警id消息。
返回值返回值为整数类型,若返回值为0表示获取消息成功(成果获取消息之后,该接口仍可重复使用),若返回值小于0,可能的原因如下:
1.返回值若为-EINVAL,msg或者register_info为空指针,建议尝试重新注册新的连接。
2.返回值若为-ENOTCONN、-EBADF,则应该是xalarmd服务异常,建议unregister之后再次执行register,get事件。
3.返回值若为-ENOMEM,则是申请内存空间失败。
4.返回值若为-EOPNOTSUPP,则表明未进行register操作或者register失败,建议重新register后再次进行get。
使用示例
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <xalarm/register_xalarm.h>

#define SLEEP_TIME 10
#define ID_LIST_LENGTH 1
#define TIME_UNIT_MILLISECONDS 1000

void process_alarm_info(struct alarm_msg* param)
{
    int alarmid;
    long long alarmtime;
    char *pucParas;

    alarmid = (param == NULL ? 0 : param->usAlarmId);
    alarmtime = (param == NULL ? 0 : ((long long)param->AlarmTime.tv_sec) * TIME_UNIT_MILLISECONDS + (long long)(param->AlarmTime.tv_usec / TIME_UNIT_MILLISECONDS));
    pucParas = (param == NULL ? NULL : param->pucParas);
    printf("plugin notified with [alarmid:%d], [alarmtime: %lld ms], [msg:%s]\n", alarmid, alarmtime, pucParas);
    return;
}

int main(int argc, char **argv)
{
    struct alarm_msg* msg;
    struct alarm_register* register_info;
    struct alarm_subscription_info id_filter;

    id_filter.id_list[0] = ALARM_REBOOT_EVENT;
    id_filter.len = ID_LIST_LENGTH;

    int ret = xalarm_register_event(&register_info, id_filter);
    if (ret < 0) {
        perror("Failed to register xalarm\n");
        return 1;
    }

    printf("Waiting for plugin msg ... \n");

    ret = xalarm_get_event(msg, register_info);
    if (ret < 0) {
        perror("Failed to get msg\n");
        return 1;
    }

    process_alarm_info(msg);
    sleep(SLEEP_TIME);

    ret = xalarm_report_event(ALARM_REBOOT_ACK_EVENT, "Reboot ACK", strlen("Reboot ACK"));
    if (ret < 0) {
        perror("Failed to send msg\n");
        return 1;
    }

    xalarm_unregister_event(&register_info);

    return 0;
}

插件事件告警订阅

sysSentry内置的告警转发子系统xalarmd通过收集巡检任务巡检过程中产生的告警信息,并将告警信息转发给订阅此类告警的用户。

python接口

告警订阅接口(回调模式)
xalarm_register
接口xalarm_register(callback: callable, id_filter: list) -> int
描述用于注册告警接收回调函数,订阅指定告警ID的告警信息
参数callback -- 告警回调函数,要求函数仅接收一个参数,用于接收告警信息对象
id_filter -- 告警ID过滤列表,长度必须为128,列表中每个元素为布尔值,表示是否订阅对应告警ID(索引0对应告警ID 1001,索引127对应告警ID 1128)
限制1. 告警ID过滤列表长度必须为128
2. 回调函数必须仅接收一个参数
3. 当前仅支持单例注册,即同一进程只能注册一次告警回调
返回值注册成功返回0,注册失败返回-1
xalarm_unregister
接口xalarm_unregister(clientId: int) -> None
描述用于注销已注册的告警回调函数,停止接收告警信息
参数clientId -- 客户端ID,整数类型
限制clientId无用, 目前仅支持单个客户端注册/解注册,因此clientId目前只能是0
返回值无返回值
xalarm_upgrade
接口xalarm_upgrade(clientId: int, id_filter: list) -> bool
描述用于更新已注册的告警ID订阅列表,动态调整订阅的告警类型
参数clientId -- 客户端ID,整数类型
id_filter -- 新的告警ID过滤列表,长度必须为128
限制1. clientId目前只能是0
2. 告警ID过滤列表长度必须为128
3. 必须先完成告警注册才能进行订阅更新
返回值更新成功返回True,更新失败返回False
xalarm_getid
接口xalarm_getid(alarm_info: Xalarm) -> int
描述从告警信息对象中获取告警ID
参数alarm_info -- 告警信息对象,类型为Xalarm
返回值返回告警ID,整数类型。若alarm_info为空则返回0
xalarm_getlevel
接口xalarm_getlevel(alarm_info: Xalarm) -> int
描述从告警信息对象中获取告警级别
参数alarm_info -- 告警信息对象,类型为Xalarm
返回值返回告警级别,整数类型,取值范围为:MINOR_ALM(一般告警)、MAJOR_ALM(严重告警)或CRITICAL_ALM(致命告警)。若alarm_info为空则返回0
xalarm_gettype
接口xalarm_gettype(alarm_info: Xalarm) -> int
描述从告警信息对象中获取告警类型
参数alarm_info -- 告警信息对象,类型为Xalarm
返回值返回告警类型,整数类型,取值范围为:ALARM_TYPE_OCCUR(告警产生)或ALARM_TYPE_RECOVER(故障恢复)。若alarm_info为空则返回0
xalarm_gettime
接口xalarm_gettime(alarm_info: Xalarm) -> int
描述从告警信息对象中获取告警产生时间
参数alarm_info -- 告警信息对象,类型为Xalarm
返回值返回告警时间,整数类型,单位为毫秒。若alarm_info为空则返回0
xalarm_getdesc
接口xalarm_getdesc(alarm_info: Xalarm) -> str
描述从告警信息对象中获取告警描述信息
参数alarm_info -- 告警信息对象,类型为Xalarm
返回值返回告警描述信息,字符串类型。若alarm_info为空或解码失败则返回None
使用示例

以下示例展示如何注册告警回调函数并处理接收到的告警信息:

python
import time
from xalarm.register_xalarm import (
    xalarm_register,
    xalarm_unregister,
    xalarm_getid,
    xalarm_getlevel,
    xalarm_gettype,
    xalarm_gettime,
    xalarm_getdesc
)

ALARM_RAS_SENTRY_EVENT = 1015

def alarm_handler(alarm_info):
    alarm_id = xalarm_getid(alarm_info)
    alarm_level = xalarm_getlevel(alarm_info)
    alarm_type = xalarm_gettype(alarm_info)
    alarm_time = xalarm_gettime(alarm_info)
    alarm_desc = xalarm_getdesc(alarm_info)

    print(f"收到告警:")
    print(f"  ID: {alarm_id}")
    print(f"  级别: {alarm_level}")
    print(f"  类型: {alarm_type}")
    print(f"  时间: {alarm_time} ms")
    print(f"  描述: {alarm_desc}")

if __name__ == "__main__":
    # 创建ID过滤器,只订阅特定告警ID的告警信息
    id_filter = [False] * 128
    id_filter[ALARM_RAS_SENTRY_EVENT - 1001] = True # 启用BMC RAS告警事件

    client_id = xalarm_register(alarm_handler, id_filter)
    if client_id != 0:
        print("告警注册失败")
        exit(1)

    print("告警注册成功,等待接收告警...")
    time.sleep(100)

    # 程序退出时注销
    xalarm_unregister(client_id)

C接口

安装相关软件包

libxalarm是sysSentry提供的C语言告警上报和订阅库,用于与xalarmd服务进行通信。使用前需要安装相关软件包:

shell
[root@openEuler ~]# yum install -y libxalarm

开发环境还需要安装libxalarm-devel包(构建依赖,非运行依赖):

shell
[root@openEuler ~]# yum install -y libxalarm-devel
头文件引用

使用libxalarm接口时,需要包含以下头文件:

c
#include <xalarm/register_xalarm.h>

编译时需要链接libxalarm库:

shell
gcc your_program.c -o your_program -lxalarm
数据结构说明
alarm_info结构体

告警信息结构体,用于socket通信格式。

c
struct alarm_info {
    unsigned short usAlarmId;       // 告警ID
    unsigned char ucAlarmLevel;     // 告警级别
    unsigned char ucAlarmType;      // 告警类别
    struct timeval AlarmTime;       // 告警生成时间戳
    char pucParas[ALARM_INFO_MAX_PARAS_LEN];  // 告警描述信息
};
alarm_msg结构体

告警消息结构体,用于事件订阅获取告警信息。

c
struct alarm_msg {
    unsigned short usAlarmId;       // 告警ID
    struct timeval AlarmTime;       // 告警生成时间戳
    char pucParas[ALARM_INFO_MAX_PARAS_LEN];  // 告警描述信息
};
alarm_subscription_info结构体

告警订阅信息结构体,用于指定订阅的告警ID列表。

c
struct alarm_subscription_info {
    int id_list[MAX_NUM_OF_ALARM_ID];  // 订阅的告警ID列表
    unsigned int len;                   // 列表长度
};
告警订阅接口(回调模式)
xalarm_Register
接口int xalarm_Register(alarm_callback_func callback, struct alarm_subscription_info id_filter);
描述注册告警订阅,通过回调函数接收告警信息
参数callback -- 告警回调函数,类型为void (*)(struct alarm_info *)
id_filter -- 告警订阅信息,指定订阅的告警ID列表
限制1. 回调函数不能为NULL
2. id_filter.len不能超过128
3. id_filter中的每个告警ID必须在1001-1128范围内
4. 不支持多线程使用,不是信号安全函数
5. 同一进程只能注册一次
6.alarm_subscription_info类型入参中len的大小需要与用户填充到id_list中告警ID的个数一致.
返回值返回0表示成功,失败返回-1

回调函数类型定义:

c
typedef void (*alarm_callback_func)(struct alarm_info *palarm);
xalarm_UnRegister
接口void xalarm_UnRegister(int client_id);
描述取消告警订阅注册,停止接收告警信息
参数client_id -- 客户端ID,当前仅支持传入0
限制client_id必须为0
返回值
xalarm_Upgrade
接口bool xalarm_Upgrade(struct alarm_subscription_info id_filter, int client_id);
描述更新订阅的告警ID列表
参数id_filter -- 新的告警订阅信息
client_id -- 客户端ID,当前仅支持传入0
限制1. 必须已经通过xalarm_Register注册
2. client_id必须为0
3. id_filter.len不能超过128
4. id_filter中的每个告警ID必须在1001-1128范围内
返回值返回true表示成功,失败返回false
xalarm_getid
接口unsigned short xalarm_getid(const struct alarm_info *palarm);
描述获取告警ID
参数palarm -- 告警信息结构体指针
返回值返回告警ID,若palarm为NULL则返回0
xalarm_getlevel
接口unsigned char xalarm_getlevel(const struct alarm_info *palarm);
描述获取告警级别
参数palarm -- 告警信息结构体指针
返回值返回告警级别(MINOR_ALM/MAJOR_ALM/CRITICAL_ALM);若palarm为NULL则返回0,表示获取不到告警 级别
xalarm_gettype
接口unsigned char xalarm_gettype(const struct alarm_info *palarm);
描述获取告警类别
参数palarm -- 告警信息结构体指针
返回值返回告警类别(ALARM_TYPE_OCCUR/ALARM_TYPE_RECOVER),若palarm为NULL则返回0
xalarm_gettime
接口long long xalarm_gettime(const struct alarm_info *palarm);
描述获取告警时间戳
参数palarm -- 告警信息结构体指针
返回值返回告警时间戳(毫秒); 返回0则表示时间存在异常
xalarm_getdesc
接口char *xalarm_getdesc(const struct alarm_info *palarm);
描述获取告警描述信息
参数palarm -- 告警信息结构体指针
返回值返回告警描述信息字符串指针,若palarm为NULL则返回NULL
使用示例
c
#include <stdio.h>
#include <xalarm/register_xalarm.h>

void alarm_handler(struct alarm_info *palarm)
{
    printf("Received alarm id: %d\n", xalarm_getid(palarm));
    printf("Alarm level: %d\n", xalarm_getlevel(palarm));
    printf("Alarm message: %s\n", xalarm_getdesc(palarm));
}

int main(int argc, char **argv)
{
    struct alarm_subscription_info id_filter;
    id_filter.id_list[0] = ALARM_OOM_EVENT;
    id_filter.id_list[1] = ALARM_PANIC_EVENT;
    id_filter.len = 2;

    int client_id = xalarm_Register(alarm_handler, id_filter);
    if (client_id != 0) {
        printf("register failed.\n");
        return -1;
    }

    // 等待接收告警...
    sleep(60);

    // 动态更新订阅列表
    struct alarm_subscription_info new_filter;
    new_filter.id_list[0] = ALARM_OOM_EVENT;
    new_filter.id_list[1] = ALARM_KERNEL_REBOOT_EVENT;
    new_filter.len = 2;

    bool ret = xalarm_Upgrade(new_filter, client_id);
    if (!ret) {
        printf("upgrade failed.\n");
    } else {
        // 等待接收新的事件告警...
        sleep(60);
    }

    xalarm_UnRegister(client_id);
    return 0;
}

日志记录

sysSentry框架运行过程中产生的日志保存在在/var/log/sysSentry/sysSentry.log中,可通过查看该日志获取框架运行详情。

插件日志记录位置和格式

开发巡检插件时,推荐插件运行过程中产生的日志保存在/var/log/sysSentry/目录下,文件名命名为[插件名].log;日志文件名称推荐和插件名称一致。

日志记录相关信息时,推荐日志格式为:<时间戳> - <日志级别> - [<文件名:行号>] - <日志消息> 其中时间戳格式为:YYYY-MM-DD HH:MM:SS,FF , 时间戳示例为:2006-01-02 15:04:05.99

日志级别可选项为:debug/info/warning/error/critical。 选项的含义如下:

  • debug:最低级别,用于记录详细的技术信息,帮助开发调试。
  • info:记录程序的正常运行信息,如启动和关闭状态。
  • warning:记录可能引起问题的情况,但不影响程序运行。
  • error:记录严重问题,导致功能失败或程序中断。
  • critical:最高级别,记录非常严重的问题,可能导致程序完全停止运行。

推荐程序正常运行时,日志级别设为info。

插件日志轮转配置

sysSentry框架及插件的日志会基于logrotate机制进行自动轮转。日志轮转是一种系统管理技术,用于管理日志文件的大小和数量,以防止日志文件占用过多的磁盘空间。

系统每次触发logrotate时会对sysSentry框架及插件的日志文件大小进行判断,如果日志文件超过4096k,则进行logrotate,一个插件/服务的日志最多轮转两次,轮转日志会被压缩。

插件日志轮转配置示例

日志轮转配置可参考/etc/logrotate.d/sysSentry文件进行配置。 当前/etc/logrotate.d/sysSentry的配置内容如下:

shell
/var/log/sysSentry/*.log{
    compress
    missingok
    notifempty
    copytruncate
    rotate 2
    size +4096k
    hourly
}

其中各项配置项含义为:

  • /var/log/sysSentry/*.log表明对哪些日志文件进行转储,*为通配符,表示所有/var/log/sysSentry/目录下以.log结尾的日志文件。
  • compress表明默认将日志文件进行压缩,节省内存空间。也可配置为nocompress,表明不压缩日志文件。推荐此项默认配置为compress。
  • missingok表明如果日志缺失,logrotate不会报错,也不会停止处理其他日志文件。推荐此配置项默认配置。
  • notifempty表明如果日志文件为空,则不进行转储。推荐此配置项默认配置。
  • copytruncate表明在日志转储时,先将当前的日志文件复制一份,然后将日志文件清空,再将复制的文件进行压缩。这通常用于正在被系统进程使用的日志文件,确保进程可以继续写入新的日志。推荐此配置项默认配置。
  • rotate 2表明在轮转操作后保留2个旧的日志文件。一旦旧的文件数量超过2个,logrotate会自动删除旧的日志文件为新的日志文件腾出空间。可根据需求自行定义所需日志数量大小。
  • size +4096k表明当日志文件大小超过4096k时,将自动触发日志转储操作。可根据需求自行定义所需日志文件大小。
  • hourly表明日志轮转的频率是每小时。可配置项有hourly/daily/weekly/monthly等。示例中表明logrotate每小时轮转日志文件。推荐此项默认配置为hourly。
更改插件日志轮转配置

对新增插件:

  1. 可更改/etc/logrotate.d/sysSentry已有的配置项内容(注:此举会更改所有/var/log/sysSentry/目录下以.log结尾的日志文件配置).

  2. 若想对/var/log/sysSentry/目录下不同的日志文件设置不同配置,可在/etc/logrotate.d/sysSentry对每一个日志文件进行单独配置,如下所示:

    shell
    /var/log/sysSentry/`[日志名一]`.log{
        # 所需配置项等
    }
    /var/log/sysSentry/`[日志名二]`.log{
        # 所需配置项等
    }

请不要对同一日志,设置不同配置。如下例所示:

shell
# A BAD EXAMPLE
/var/log/sysSentry/*.log{
    compress
}
/var/log/sysSentry/example.log{
    nocompress
}

在该示例中,*为通配符,表示所有/var/log/sysSentry/目录下以.log结尾的日志文件,已经包含了example.log,设置为轮转日志需要压缩;但配置文件中又单独设置example.log不需要压缩。该做法可能会造成预期之外的行为。

如果手动修改了/etc/logrotate.d/sysSentry文件,可通过logrotate -f /etc/logrotate.d/sysSentry手动触发日志轮转。 logrotate的其他使用方法请参考https://linux.die.net/man/8/logrotate

结果上报

用户可通过sysSentry提供的接口将插件巡检结果上报给sysSentry服务,并通过get_result命令查看结果:

shell
[root@openEuler ~]# sentryctl get_result <插件名>

提供python与c两种语言的对外接口。

结果上报使用限制

  1. 结果上报接口应在巡检插件运行结束前被调用。
  2. 巡检插件的一个生命中期内仅应上报一次巡检结果。
  3. 巡检插件运行成功或失败均应该上报巡检结果。

python巡检结果上报接口

结构体 ResultLevel

python
class ResultLevel(Enum):
    """result level for report_result"""
    PASS = 0    # 巡检任务正常运行结束,无异常
    FAIL = 1    # 因缺少依赖、环境不支持等原因跳过/未执行巡检任务
    SKIP = 2    # 因执行命令错误等原因,巡检任务执行失败
    MINOR_ALM = 3   # 巡检任务结束,存在系统存在异常,并且已经尝试自动隔离等方式完成修复
    MAJOR_ALM = 4   # 巡检任务结束,系统存在异常,需要通过重启等方式修复
    CRITICAL_ALM = 5    # 巡检任务结束,致命告警,系统或硬件存在无法修复问题,建议更换

接口 结果上报

接口int report_result(task_name: str, result_level : ResultLevel, report_data : str) -> int
描述用于模块工具向sysSentry上报巡检结果
参数task_name -- 巡检任务名称
result_level -- 巡检结果异常等级,可选参数请参考ResultLevel结构体
report_data -- 巡检结果详细信息,应为json格式转换而成的字符串
返回值正常:0,异常:非0

示例:

python
[root@openEuler ~]# python3
Python 3.7.9 (default, Dec 11 2023, 19:40:40) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import json
>>> from syssentry.result import ResultLevel, report_result
>>> report_result("test", ResultLevel.PASS, json.dumps({}))
0

c巡检结果上报接口

需要安装libxalarm软件包:

shell
[root@openEuler ~]# yum install -y libxalarm

开发环境还需要安装libxalarm-devel包(构建依赖,非运行依赖):

shell
[root@openEuler ~]# yum install -y libxalarm-devel

结构体 RESULT_LEVEL

c
enum RESULT_LEVEL {
    RESULT_LEVEL_PASS = 0,  // 巡检任务正常运行结束,无异常
    RESULT_LEVEL_FAIL = 1,  // 因缺少依赖、环境不支持等原因跳过/未执行巡检任务
    RESULT_LEVEL_SKIP = 2,  // 因执行命令错误等原因,巡检任务执行失败
    RESULT_LEVEL_MINOR_ALM = 3, // 巡检任务结束,存在系统存在异常,并且已经尝试自动隔离等方式完成修复
    RESULT_LEVEL_MAJOR_ALM = 4, // 巡检任务结束,系统存在异常,需要通过重启等方式修复
    RESULT_LEVEL_CRITICAL_ALM = 5,  // 巡检任务结束,致命告警,系统或硬件存在无法修复问题,建议更换
};

接口:结果上报

接口int report_result(const char *task_name, enum RESULT_LEVEL result_level, const char *report_data);
描述用于模块工具向sysSentry上报巡检结果
参数task_name:巡检任务名称
result_level:巡检结果异常等级,可选参数请参考ResultLevel结构体
report_data:巡检结果详细信息,应为json格式转换而成的字符串
返回值正常:0,异常:非0

示例:

shell
[root@openEuler ~]# cat report_res.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <xalarm/register_xalarm.h>

int main(int argc, char *argv[]) {
    enum RESULT_LEVEL result_lv = 0;
    result_lv = RESULT_LEVEL_PASS;
    details = "{\"a\": 1, \"b\": 2}";
    int res = report_result(task_name, result_lv, details);
    if (res == -1) {
        printf("failed send result to sysSentry\n");
    }

    return 0;
}
[root@openEuler ~]# gcc report_res.c -o report_res -lxalarm

对接sentryCollector采集服务

sysSentry软件中包含用来做数据采集的服务:sentryCollector,用户可通过sentryCollector服务定期采集系统数据。

采集使用限制

  1. 当前仅支持对系统io数据进行采集,并提供ebpf采集和内核无锁采集两种方案;
  2. 仅支持对nvme ssd(nvme固态硬盘)、sata ssd(sata固态硬盘)、sata hdd(sata机械硬盘)三种磁盘的数据进行采集;
  3. sentryCollector的io数据采集默认使用ebpf采集,如需使用内核无锁采集,请参考常见问题解决方法 - Q1进行部署。

io数据采集

sentryCollector支持按周期采集指定磁盘的数据,并提供两种采集方式:

  • 内核无锁采集 -- 由内核进行数据采集,并将采集结果上报给用户态读取,需要重编内核实现。
  • ebpf采集 -- 通过使用ebpf向内核打点的方式进行采集,无需重编内核。

内核无锁采集和ebpf采集的差异

内存无锁采集和ebpf采集在以下几个方面存在区别:

  1. 支持采集的阶段不同

    将一个io从发生到结束分成多个不同的阶段,以下是两种采集方式支持的阶段类型:

    阶段biorq_driverthrotlwbtgettagplugdeadlinebpfrequeuehctx
    内核无锁采集支持支持支持支持支持支持支持支持支持支持
    ebpf采集支持支持不支持支持支持不支持不支持不支持不支持不支持
  2. 支持采集的IO类型不同

    IO类型内核无锁采集ebpf采集数据含义
    read支持支持读IO
    write支持支持写IO
    flush支持不支持flush IO
    discard支持不支持discard IO

除以上两个差异之外,内核无锁采集和ebpf采集方案可采的数据类型、支持系统等数据均相同:

  1. 两种采集方案均支持4.19内核,x86_64和aarch64架构。
  2. 两种采集方案均支持采集nvme ssd(nvme固态硬盘)、sata ssd(sata固态硬盘)和sata hdd(sata机械硬盘)三种盘的数据。
  3. 两种采集方案均支持对latency、iodump、iolength、iops数据的采集:
    • latency -- 指定周期内的时延数据;
    • iodump -- 指定周期内超时未完成的io数量,如io运行超过1秒未完成即为超时;
    • iolength -- io队列长度
    • iops -- 每秒内完成的io数量

对外接口

当前仅提供python语言的接口,需要用户安装pysentry_collect软件包:

shell
[root@openEuler ~]# yum install -y pysentry_collect

接口一 查看磁盘类型

接口get_disk_type(disk)
描述从采集模块中查询磁盘类型
参数disk – 磁盘名,例:sda,必选参数
限制磁盘名不超过32个字符
返回值返回值格式为:{"ret": value1, "message":value2}
value1取值为0或者其他正整数,0表示成功,其他非零表示失败;
message是个字符串,表示表示磁盘类型,字符串类型,如果ret为非零,则message为空字符串,当前支持的message对应磁盘类型如下:
"message": "0" -- nvme_ssd
"message": "1" -- sata_ssd
"message": "2" -- sata_hdd
返回值示例:
- 磁盘类型不支持:{"ret": 8, "message": ""} # ret结果非0
- 函数执行成功:{"ret": 0, "message": "1"} # 磁盘为sata ssd类型

示例:

shell
[root@openEuler ~]# python3
Python 3.7.9 (default, Dec 11 2023, 19:40:40) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from sentryCollector.collect_plugin import get_disk_type, Disk_Type
>>> res = get_disk_type("sda")
>>> res
{'ret': 0, 'message': '1'}
>>> curr_disk_type = int(res['message'])
>>> curr_disk_type
1
>>> Disk_Type[curr_disk_type]
'sata_ssd'

接口二 查询采集是否合法

接口is_iocollect_valid(period, disk_list=None, stage=None)
描述确认是否在采集范围内,确认周期是否合法
参数period – 用户采集周期,整形,单位秒,必选参数
disk_list – 磁盘列表,默认为None,代表关注所有磁盘,可选参数。可传入自定义列表,例:["sda", "sdb", "sdv"]
stage – 采集阶段,默认为None,代表关注所有采集阶段,可选参数。可传入自定义阶段列表,例:["wbt", "bio"]
限制1. 采集周期取值在1到300之间
2. 磁盘列表磁盘个数不超过10个,如果超过10个,默认取前10个,磁盘列表种的磁盘名不超过32个字符
3. 采集阶段个数不超过15个,阶段名字符不超过20个字符
返回值返回值格式为:{"ret": value1, "message":value2}
value1取值为0或者其他正整数,0表示成功,其他非零表示失败
message是个字符串,表示有效的磁盘和该磁盘对应的stage,字符串类型,如果字符串为空说明全都不支持,格式如下:
{"disk_name1": ["stage1", "stage2"], "disk_name2": ["stage1", "stage2"], ...}
返回值示例:
- 验证失败:{"ret": 1, "message": {}} # ret结果非0
- 验证成功,所有盘均不支持采集:{"ret": 0, "message": {}}
- 部分盘不支持(message中返回支持的盘和对应的阶段):{"ret": 0, "message": {"sda": ["bio", "gettag"], "sdb": ["bio", "gettag"]}}

示例:

shell
[root@openEuler ~]# python3
Python 3.7.9 (default, Dec 11 2023, 19:40:40) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from sentryCollector.collect_plugin import is_iocollect_valid
>>> is_iocollect_valid(1, ["sda"])
{'ret': 0, 'message': '{"sda": ["throtl", "wbt", "gettag", "plug", "deadline", "hctx", "requeue", "rq_driver", "bio"]}'}

接口三 查询指定数据

接口get_io_data(period, disk_list, stage, iotype)
功能确认是否在采集范围内,确认周期是否合法
参数period – 用户采集周期,整形,单位秒,必选参数
disk_list -- 磁盘列表,必选参数,例:["sda", "sdb", "sdv"]
stage – 读取的阶段,必选参数,例:["bio", "gettag", "wbt"]。
iotype – IO类型,列表中对应要获取的IO数据类型,仅支持read/write/flush/discard,必选参数,例:["read", "write"]
限制1. 采集周期取值在1到300之间,并且为period_time值的整数倍,且倍数不超过max_save(两个数值请参考/etc/sysSentry/sentryCollector.conf)
2. 磁盘列表磁盘个数不超过10个,如果超过10个,默认取前10个,磁盘列表种的磁盘名不超过32个字符
3. 采集阶段个数不超过15个,阶段名字符不超过20个字符
4. IO类型个数不超过4个,字符长度不超过7个(最长的长度是discard)
返回值返回值格式为:{"ret": value1, "message":value2}
value1取值为0或者其他正整数,0表示成功,其他非零表示失败
message是个字符串,表示采集模块处理过的当前周期数据,字符串类型,格式如下:
"{"disk_name1": {"stage1": {"read": [latency, iodump, iolength, iops],"write": [write_latency, write_iodump, iolength, iops]},"stage2": {…}},…}"
返回值示例:
- 获取数据失败:{"ret": 1, "message": {}} # ret结果非0
- 获取数据成功:{"ret": 0, "message": "{"sda": {"bio": {"read": [0.1, 0, 100, 19], "write": [0.5, 3, 100, 12]}, "wbt": {}}, "sdb"…}"}
其中[0.5, 3, 100, 12]对应[时延ns,iodumps数量,io队列长度,iops]

示例:

shell
[root@openEuler ~]# python3
Python 3.7.9 (default, Dec 11 2023, 19:40:40) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from sentryCollector.collect_plugin import get_io_data
>>> get_io_data(1, ["sda"], ["bio"], ["read"])
{'ret': 0, 'message': '{"sda": {"bio": {"read": [0, 0, 0, 0]}}}'}