Zabbix源码解析之memory监控项值的采集与传递

vm.memory.size监控项

用法: vm.memory.size[<mode>]

mode参数:

  • total (*) - 总物理内存. mode的默认值
  • free (*) - 可用内存.
  • active - 内存当前使用或最近使用,它在RAM中是活跃的。
  • inactive - 未使用内存.
  • wired - 被标记为始终驻留在RAM中的内存,不会移动到磁盘。
  • pinned - 同“wired”。
  • anon - 与文件无关的内存(不能重新读取)。
  • exec - 可执行代码,通常来自于一个(程序)文件。
  • file - 缓存最近访问文件的目录。
  • buffers (*) - 缓存磁盘读写数据。
  • cached (*) - 缓存文件系统读写数据。
  • shared - 可以同时被多个进程访问的内存。
  • used (*) - 已使用内存。
  • pused (*) - 已使用内存占总内存的百分比。
  • available (*) - 可用内存
  • pavailable (*) - 可用内存占总内存的百分比。

其中,Linux 2.6及之后的系统所支持的参数,已用“*”标出。

vm.memory.size监控项在初始化数据库时,就已经被插入到数据库的items表中。

INSERT INTO `items` (...) values ('10026','0','','','10001','Total memory','vm.memory.size[total]','1h','1w','365d','0','3','','B','','0','','','','',NULL,NULL,'','','0','','','','','0',NULL,'','','0','0','0','0','','0','',NULL,'3s','','','','200','1','0','','0','0','0','0','0','0');
INSERT INTO `items` (...) values ('22181','0','','','10001','Available memory','vm.memory.size[available]','1m','1w','365d','0','3','','B','','0','','','','',NULL,NULL,'','','0','','','','','0',NULL,'','Available memory is defined as free+cached+buffers memory.','0','0','0','0','','0','',NULL,'3s','','','','200','1','0','','0','0','0','0','0','0');

监控项实现函数

vm.memory.size监控项在不同的操作系统下实现各不相同,Linux系统下的实现,在src/libs/zbxsysinfo/linux/linux.c中。其配置项存放于parameters_specific数组中,对应的实现函数为VM_MEMORY_SIZE,数组的类型为ZBX_METRIC

ZBX_METRIC parameters_specific[] =
/*  KEY         FLAG        FUNCTION        TEST PARAMETERS */
{
    ...
    {"vm.memory.size",  CF_HAVEPARAMS,  VM_MEMORY_SIZE,     "total"},
    ...
    {"system.cpu.load", CF_HAVEPARAMS,  SYSTEM_CPU_LOAD,    "all,avg1"},
    ...
​
    {NULL}
};

相关的结构体

struct ZBX_METRIC

typedef struct
{
    char        *key;
    unsigned    flags;
    int         (*function)(AGENT_REQUEST *request, AGENT_RESULT *result);
    char        *test_param;    /* item test parameters; user parameter items keep command here */
}
ZBX_METRIC;

该结构体用于记录zabbix的监控项信息

  • key:监控项的键值
  • flags:标志,可以是CF_HAVEPARAMS0(取决于监控项是否接受参数)。CF_HAVEPARAMS(item accepts either optional or mandatory parameters)。
  • function:函数,实现这个监控项的函数
  • test_param:测试参数,当zabbix客户端带有-p标志启动时,这个参数列表会被用到。
struct AGENT_REQUEST/* agent request structure */
typedef struct
{
    char        *key;     // 监控项键值
    int     nparam;       // 请求中的参数个数
    char        **params; // 请求中的每个参数值
    zbx_uint64_t    lastlogsize;
    int     mtime;
}
AGENT_REQUEST;struct AGENT_RESULT/* agent return structure */
typedef struct
{
    zbx_uint64_t    lastlogsize;    /* meta information */
    zbx_uint64_t    ui64;  // 返回的int值
    double      dbl;       // 返回的double值
    char        *str;      // 返回的字符串值
    char        *text;
    char        *msg;       /* possible error message */
    zbx_log_t   *log;
    int     type;       /* flags: see AR_* above */
    int     mtime;      /* meta information */
}
AGENT_RESULT;

VM_MEMORY_SIZE函数

vm.memory.size键值的功能是由VM_MEMORY_SIZE函数实现的。位置在:src/libs/zbxsysinfo/linux/memory.c

int VM_MEMORY_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result)
{
    char    *mode;
    int ret;
​
    // 如果参数个数大于1,则不合法,参数个数太多。
    if (1 < request->nparam)
    {
        SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
        return SYSINFO_RET_FAIL;
    }
​
    // 从request中取得mode值
    mode = get_rparam(request, 0);
​
    if (NULL == mode || '\0' == *mode || 0 == strcmp(mode, "total")) // 默认mode值为:"total"
        ret = VM_MEMORY_TOTAL(result);
    else if (0 == strcmp(mode, "free"))
        ret = VM_MEMORY_FREE(result);
    else if (0 == strcmp(mode, "buffers"))
        ret = VM_MEMORY_BUFFERS(result);
    else if (0 == strcmp(mode, "used"))
        ret = VM_MEMORY_USED(result);
    else if (0 == strcmp(mode, "pused"))
        ret = VM_MEMORY_PUSED(result);
    else if (0 == strcmp(mode, "available"))
        ret = VM_MEMORY_AVAILABLE(result);
    else if (0 == strcmp(mode, "pavailable"))
        ret = VM_MEMORY_PAVAILABLE(result);
    else if (0 == strcmp(mode, "shared"))
        ret = VM_MEMORY_SHARED(result);
    else if (0 == strcmp(mode, "cached"))
        ret = VM_MEMORY_PROC_MEMINFO("Cached:", result);
    else if (0 == strcmp(mode, "active"))
        ret = VM_MEMORY_PROC_MEMINFO("Active:", result);
    else if (0 == strcmp(mode, "anon"))
        ret = VM_MEMORY_PROC_MEMINFO("AnonPages:", result);
    else if (0 == strcmp(mode, "inactive"))
        ret = VM_MEMORY_PROC_MEMINFO("Inactive:", result);
    else if (0 == strcmp(mode, "slab"))
        ret = VM_MEMORY_PROC_MEMINFO("Slab:", result);
    else
    {
        SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
        ret = SYSINFO_RET_FAIL;
    }
​
    return ret;
}

从源码中可以看到,Linux系统支持的模式包括如下参数,与官方文档中所列的参数不同。

  • total
  • free
  • buffers
  • used
  • pused
  • available
  • pavailable
  • shared
  • cached
  • active
  • anon
  • inactive
  • slab

其中各个参数指标值的获取,可以分为2种方法:

  • 第一种:调用sysinfo函数获取指标值。通过这种方式获取的选项参数有:total,free,buffers,used,pused,pavailable,shared。
  • 第二种:读取/proc/meminfo文件中的指标值。通过这种方式获取的选项参数有:available,cached,active,anon,inactive,slab。

下面我们分别对两种情况进行分析。

调用sysinfo函数

static int  VM_MEMORY_TOTAL(AGENT_RESULT *result)
{
    // 定义存储结果的结构体
    struct sysinfo  info;
​
    // 调用sysinfo函数
    if (0 != sysinfo(&info))
    {
        SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain system information: %s", zbx_strerror(errno)));
        return SYSINFO_RET_FAIL;
    }
​
    // 设置返回值
    SET_UI64_RESULT(result, (zbx_uint64_t)info.totalram * info.mem_unit);
​
    return SYSINFO_RET_OK;
}

1、Linux中sysinfo()函数是用来获取系统相关统计信息的函数。它会将结果存储在struct sysinfo结构体中。

函数声明: int sysinfo(struct sysinfo *info);

2、struct sysinfo的定义如下:

struct sysinfo { 
      long uptime;                  /* 启动到现在经过的时间 */ 
      unsigned long loads[3];       /* 1, 5, and 15 分钟平均负载 */ 
      unsigned long totalram;       /* 总的可用的内存大小 */ 
      unsigned long freeram;        /* 可用的内存大小 */ 
      unsigned long sharedram;      /* 共享内存的大小*/ 
      unsigned long bufferram;      /* buffer内存的大小 */ 
      unsigned long totalswap;      /* 交换区大小 */ 
      unsigned long freeswap;       /* 可用的交换区大小 */ 
      unsigned short procs;         /* 当前进程数目 */ 
      unsigned short pad;           /* Explicit padding for m68k */
      unsigned long totalhigh;      /* 总的high memory大小 */ 
      unsigned long freehigh;       /* 可用的high memory大小 */ 
      unsigned int mem_unit;        /* 以字节为单位的内存单元大小 */ 
      char _f[20-2*sizeof(long)-sizeof(int)];    /* libc5的补丁 */
};

3、total,free,buffers,used,pused,pavailable,shared等指标,都是以struct sysinfo中的成员的取值来计算的。

  • total:info.totalram * info.mem_unit
  • free:info.freeram * info.mem_unit
  • buffers:`info
  • ram * info.mem_unit`
  • used:(info.totalram - info.freeram) * info.mem_unit
  • pused:(info.totalram - info.freeram) / (double)info.totalram * 100
  • pavailable:available / (info.totalram * info.mem_unit) * 100(available在/proc/meminfo文件中读出)
  • shared:info.sharedram * info.mem_unit(仅Linux 2.4)

读取/proc/meminfo文件

1、功能在VM_MEMORY_PROC_MEMINFO函数中实现。向meminfo_entry参数传递"Cached:", "Active:", "AnonPages:", "Inactive:", "Slab:"字段。

2、available的获取比较特殊,它先检测/proc/meminfo文件文件中是否有"MemAvailable:"字段,如果没有,则再调用sysinfo函数获取。

static int  VM_MEMORY_PROC_MEMINFO(const char *meminfo_entry, AGENT_RESULT *result)
{
    FILE        *f;
    zbx_uint64_t    value;
    int     ret = SYSINFO_RET_FAIL;
​
    if (NULL == (f = fopen("/proc/meminfo", "r")))
    {
        SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open /proc/meminfo: %s", zbx_strerror(errno)));
        return SYSINFO_RET_FAIL;
    }
​
    if (SUCCEED == byte_value_from_proc_file(f, meminfo_entry, NULL, &value))
    {
        SET_UI64_RESULT(result, value);
        ret = SYSINFO_RET_OK;
    }
    else
        SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain value from /proc/meminfo."));
​
    zbx_fclose(f);
​
    return ret;
}
​
static int  VM_MEMORY_AVAILABLE(AGENT_RESULT *result)
{
    FILE        *f;
    zbx_uint64_t    value;
    struct sysinfo  info;
    int     res, ret = SYSINFO_RET_FAIL;
​
    /* try MemAvailable (present since Linux 3.14), falling back to a calculation based on sysinfo() and Cached */
    // 打开"/proc/meminfo"文件
    if (NULL == (f = fopen("/proc/meminfo", "r")))
    {
        SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open /proc/meminfo: %s", zbx_strerror(errno)));
        return SYSINFO_RET_FAIL;
    }
​
    // 抽取"MemAvailable:"字段的值,"Cached:"是一个守护字段,即:获取在"Cached:"字段之前的"MemAvailable:"字段
    if (FAIL == (res = byte_value_from_proc_file(f, "MemAvailable:", "Cached:", &value)))
    {
        SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain the value of MemAvailable from /proc/meminfo."));
        goto close;
    }
​
    // 获取成功,则设置返回值
    if (SUCCEED == res)
    {
        SET_UI64_RESULT(result, value);
        ret = SYSINFO_RET_OK;
        goto close;
    }
​
    if (FAIL == (res = byte_value_from_proc_file(f, "Cached:", NULL, &value)))
    {
        SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain the value of Cached from /proc/meminfo."));
        goto close;
    }
​
    if (NOTSUPPORTED == res)
        value = 0;
​
    // "/proc/meminfo"文件中没有得到没有,则再调用sysinfo获得值
    if (0 != sysinfo(&info))
    {
        SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain system information: %s", zbx_strerror(errno)));
        goto close;
    }
​
    // 获取成功,则设置返回值
    SET_UI64_RESULT(result, (zbx_uint64_t)(info.freeram + info.bufferram) * info.mem_unit + value);
    ret = SYSINFO_RET_OK;
close:
    zbx_fclose(f);
​
    return ret;
}
​
int byte_value_from_proc_file(FILE *f, const char *label, const char *guard, zbx_uint64_t *bytes)
{
    char    buf[MAX_STRING_LEN], *p_value, *p_unit;
    size_t  label_len, guard_len;
    long    pos = 0;
    int ret = NOTSUPPORTED;
​
    label_len = strlen(label);
    p_value = buf + label_len;
​
    if (NULL != guard)
    {
        guard_len = strlen(guard);
        pos = ftell(f);
    }
​
    // 当读取到一个换行符号“\n”并存储到缓冲区之后就停止读取;或者缓冲区内存储的字符数达到buf_size-1个时,也停止读取。
    while (NULL != fgets(buf, (int)sizeof(buf), f))
    {
        if (NULL != guard)
        {
            // 如果已经读到guard字段,则停止
            if (0 == strncmp(buf, guard, guard_len))
            {
                fseek(f, pos, SEEK_SET);
                break;
            }
​
            // 返回文件指针位置距离文件起始位置的偏移量
            pos = ftell(f);
        }
​
        // 如果判断不是label字段,则continue,继续读下一个字段
        if (0 != strncmp(buf, label, label_len))
            continue;
​
        // 程序执行到这儿,说明已经遇到了label字段,buf中存储了label这一行的值
        // 例如:MemAvailable:    1198620 kB
        // 前面已经初始化了p_value的值:p_value = buf + label_len; 则p_value指向的位置在MemAvailable:后的那个字节
        // 提取label字段的单位,反向寻找空格,空格以及空格之后的内容存储到p_unit字符串中,如果没有读到,报错
        if (NULL == (p_unit = strrchr(p_value, ' ')))
        {
            ret = FAIL;
            break;
        }
​
        // p_unit当前的位置:指向上面找到的空格,把空格置为'\0',且p_unit++,p_unit最终指向'k'这个字节
        // 这会把一行字符串分为2行,p_value:    1198620,p_unit:kB
        *p_unit++ = '\0';
​
        // 略过空格,由于上面已将整行字符串拆分为2个,因此p_value指向的就是"1198620"这个值。
        while (' ' == *p_value)
            p_value++;
​
        // 验证p_value所指的字符串是否一个uint64类型的整型值,是的话就设置到bytes中
        if (FAIL == is_uint64(p_value, bytes))
        {
            ret = FAIL;
            break;
        }
​
        // 去掉p_unit最后的换行符
        zbx_rtrim(p_unit, "\n");
​
        // 将"kB","mB","GB","TB"等单位转换为字节
        if (0 == strcasecmp(p_unit, "kB"))
            *bytes <<= 10;
        else if (0 == strcasecmp(p_unit, "mB"))
            *bytes <<= 20;
        else if (0 == strcasecmp(p_unit, "GB"))
            *bytes <<= 30;
        else if (0 == strcasecmp(p_unit, "TB"))
            *bytes <<= 40;
​
        ret = SUCCEED;
        break;
    }
​
    return ret;
}

监控项的设置

init_metrics函数

1、vm.memory.size监控项的实现我们已经分析完了,它会被存储到parameters_specific数组中。那么parameters_specific数组在哪儿被使用到呢?在init_metrics函数中。

void    init_metrics(void)
{
    int i;
    char    error[MAX_STRING_LEN];
​
    commands = (ZBX_METRIC *)zbx_malloc(commands, sizeof(ZBX_METRIC));
    commands[0].key = NULL;
​
    ...
​
    // 如果定义了WITH_SPECIFIC_METRICS宏,则遍历parameters_specific数组,
    // 调用add_metric函数,将每个监控项添加到commands数组中。
​
#ifdef WITH_SPECIFIC_METRICS
    for (i = 0; NULL != parameters_specific[i].key; i++)
    {
        if (SUCCEED != add_metric(&parameters_specific[i], error, sizeof(error)))
        {
            zabbix_log(LOG_LEVEL_CRIT, "cannot add item key: %s", error);
            exit(EXIT_FAILURE);
        }
    }
#endif
    ...
}

2、init_metrics函数在zabbix_server和zabbix_agent启动的过程中都被调用了,因此这些监控项在zabbix启动时已经被设置好了。

add_metric函数

1、commands是在sysinfo.c中定义的一个ZBX_METRIC结构体变量,初始值是NULL

static ZBX_METRIC   *commands = NULL;
​
// 分配初始内存,只分配一个ZBX_METRIC结构体变量大小的内存
commands = (ZBX_METRIC *)zbx_malloc(commands, sizeof(ZBX_METRIC));
commands[0].key = NULL;

2、add_metric函数会向commands数组中添加值。

int add_metric(ZBX_METRIC *metric, char *error, size_t max_error_len)
{
    int i = 0;
​
    while (NULL != commands[i].key)
    {
        // 比较commands中的key与metric中的key,如果commands中已存在,表明该metric已经添加过了,直接返回
        if (0 == strcmp(commands[i].key, metric->key))
        {
            zbx_snprintf(error, max_error_len, "key \"%s\" already exists", metric->key);
            return FAIL;    /* metric already exists */
        }
        i++;
    }
​
    // 将metric中的值复制到commands中
    commands[i].key = zbx_strdup(NULL, metric->key);
    commands[i].flags = metric->flags;
    commands[i].function = metric->function;
    commands[i].test_param = (NULL == metric->test_param ? NULL : zbx_strdup(NULL, metric->test_param));
​
    // zbx_realloc实现内存扩展。commands的内存不是一次性分配好的,而是在调用add_metric时不断扩展的。
    // 内存分配开始是一个ZBX_METRIC结构体的大小的空位,开始时数据填充在空位(上面给commands[i]赋值时),
    // 然后重新分配一个空间(使用zbx_realloc),并设置最后一个空位为0值。
    commands = (ZBX_METRIC *)zbx_realloc(commands, (i + 2) * sizeof(ZBX_METRIC));
    memset(&commands[i + 1], 0, sizeof(ZBX_METRIC));
​
    return SUCCEED;
}

监控项实现函数的调用

process函数

commands数组在哪里被使用到了呢?在process函数中,process函数定义在src/libs/zbxsysinfo/sysinfo.c中。

/******************************************************************************
 *                                                                            *
 * Function: process                                                          *
 *                                                                            *
 * Purpose: execute agent check                                               *
 *                                                                            *
 * Parameters: in_command - item key  --监控项键值                              *
 *             flags - PROCESS_LOCAL_COMMAND, allow execution of system.run   *
 *                     PROCESS_MODULE_COMMAND, execute item from a module     *
 *                     PROCESS_WITH_ALIAS, substitute agent Alias             *
 *                                                                            *
 * Return value: SUCCEED - successful execution                               *
 *               NOTSUPPORTED - item key is not supported or other error      *
 *               result - contains item value or error message --采集到的结果   *
 *                                                                            *
 ******************************************************************************/
int process(const char *in_command, unsigned flags, AGENT_RESULT *result)
{
    int     ret = NOTSUPPORTED;
    ZBX_METRIC  *command = NULL;
    AGENT_REQUEST   request;
​
    // 初始化AGENT_REQUEST结构体中各成员的值
    init_request(&request);
​
    // 如果有PROCESS_WITH_ALIAS标志,则取in_command键值的别名,并根据键值填充AGENT_REQUEST结构体中各成员的值
    if (SUCCEED != parse_item_key((0 == (flags & PROCESS_WITH_ALIAS) ? in_command : zbx_alias_get(in_command)),
            &request))
    {
        SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid item key format."));
        goto notsupported;
    }
​
    /* system.run is not allowed by default except for getting hostname for daemons */
    if (1 != CONFIG_ENABLE_REMOTE_COMMANDS && 0 == (flags & PROCESS_LOCAL_COMMAND) &&
            0 == strcmp(request.key, "system.run"))
    {
        SET_MSG_RESULT(result, zbx_strdup(NULL, "Remote commands are not enabled."));
        goto notsupported;
    }
​
    // 遍历commands数组,查找与request.key相同的监控项
    for (command = commands; NULL != command->key; command++)
    {
        if (0 == strcmp(command->key, request.key))
            break;
    }
​
    // 如果没找到,则报"Unsupported item key."
    if (NULL == command->key)
    {
        SET_MSG_RESULT(result, zbx_strdup(NULL, "Unsupported item key."));
        goto notsupported;
    }
​
    // 如果该监控项带了PROCESS_MODULE_COMMAND标志,但command->flags中没有设置CF_MODULE标志,则报"Unsupported item key."
    if (0 != (flags & PROCESS_MODULE_COMMAND) && 0 == (command->flags & CF_MODULE))
    {
        SET_MSG_RESULT(result, zbx_strdup(NULL, "Unsupported item key."));
        goto notsupported;
    }
​
    // 如果command->flags中没有设置CF_HAVEPARAMS标志,但调用时却指定了参数,则报"Item does not allow parameters."
    if (0 == (command->flags & CF_HAVEPARAMS) && 0 != request.nparam)
    {
        SET_MSG_RESULT(result, zbx_strdup(NULL, "Item does not allow parameters."));
        goto notsupported;
    }
​
    // CF_USERPARAMETER: item is defined as user parameter
    if (0 != (command->flags & CF_USERPARAMETER))
    {
        if (0 != (command->flags & CF_HAVEPARAMS))
        {
            char    *parameters = NULL, error[MAX_STRING_LEN];
​
            if (FAIL == replace_param(command->test_param, &request, &parameters, error, sizeof(error)))
            {
                SET_MSG_RESULT(result, zbx_strdup(NULL, error));
                goto notsupported;
            }
​
            free_request_params(&request);
            add_request_param(&request, parameters);
        }
        else
        {
            free_request_params(&request);
            add_request_param(&request, zbx_strdup(NULL, command->test_param));
        }
    }
​
    // 调用监控项的实现函数,对vm.memory.size来说,就是VM_MEMORY_SIZE函数
    if (SYSINFO_RET_OK != command->function(&request, result))
    {
        /* "return NOTSUPPORTED;" would be more appropriate here for preserving original error */
        /* message in "result" but would break things relying on ZBX_NOTSUPPORTED message. */
        if (0 != (command->flags & CF_MODULE) && 0 == ISSET_MSG(result))
            SET_MSG_RESULT(result, zbx_strdup(NULL, ZBX_NOTSUPPORTED_MSG));
​
        goto notsupported;
    }
​
    ret = SUCCEED;
​
notsupported:
    // 释放request占用的内存
    free_request(&request);
​
    return ret;
}

agent中调用process函数的地方

process函数中会最终调用监控项实现函数,那process函数在哪被调用到的呢? 在zabbix_agent中,调用process函数的地方有2处,分别位于agent的被动模式和主动模式的实现中。我们分别来分析一下。

被动模式下的process_listener函数

主动模式下的process_active_checks函数

监控项值的序列化与传递

agent与server间的通信协议

agent与server间的通信协议比较简单,其协议格式为:<PROTOCOL><FLAGS><DATALEN><RESERVED><DATA>

  • PROTOCOL: 协议头,该字段长度为4个字节,内容为"ZBXD"。
  • FLAGS: 协议标志,该字段长度为1个字节。有2个取值(这两个值可以使用“或”操作同时取):
  • 0x01:ZBX_TCP_PROTOCOL,zabbix TCP通信协议
  • 0x02:ZBX_TCP_COMPRESS,使用压缩算法
  • DATALEN: 数据长度,该字段长度为4个字节。整型,以小端模式表示。
  • 注意该长度不包含协议头这几个字段的长度,它仅表示DATA字段的数据长度。
  • RESERVED: 保留字段,用作协议扩展,字段长度为4字节。当ZBX_TCP_COMPRESS标志被设置后,RESERVED字段会保存未被压缩时的数据段的长度。整型,以小端模式表示。
  • DATA: 数据内容,使用JSON格式来序列化。

监控项值的传递

1、我们以主动模式下会被调用的send_buffer函数(zbx_tcp_send()最终会调用到zbx_tcp_send_ext()函数)为例,分析一下监控项值如何发送给server。

被动模式下zbx_tcp_send_to(s, *value, ...)函数最终也会调用到zbx_tcp_send_ext()函数,与主动模式下最终的处理是一致的,我们不再单独分析。

/******************************************************************************
 *                                                                            *
 * Function: send_buffer                                                      *
 *                                                                            *
 * Purpose: Send value stored in the buffer to Zabbix server                  *
 *                                                                            *
 * Parameters: host - Zabbix server IP地址或Hostname                           *
 *             port - Zabbix server 端口                                       *
 *             该函数没有监控项值参数,它会发送缓冲区buffer中的所有值               *
 *                                                                            *
 ******************************************************************************/
static int  send_buffer(const char *host, unsigned short port)
{
    ZBX_ACTIVE_BUFFER_ELEMENT   *el;
    int             ret = SUCCEED, i, now;
    const char          *err_send_step = "";
    zbx_socket_t            s;
    // json参数中存储序列化后的JSON格式的监控项值
    struct zbx_json         json;
​
    // 初始化json对象各个成员: {}
    zbx_json_init(&json, ZBX_JSON_STAT_BUF_LEN);
    // 在json对象中添加字符串元素 "request":"sender data"
    zbx_json_addstring(&json, ZBX_PROTO_TAG_REQUEST, ZBX_PROTO_VALUE_AGENT_DATA, ZBX_JSON_TYPE_STRING);
    // 在json对象中添加字符串元素 "session": MD5_session_token
    zbx_json_addstring(&json, ZBX_PROTO_TAG_SESSION, session_token, ZBX_JSON_TYPE_STRING);
    // 在json对象中添加一个数组 "data":[]
    zbx_json_addarray(&json, ZBX_PROTO_TAG_DATA);
​
    // 循环发送buffer中的指标项值
    for (i = 0; i < buffer.count; i++)
    {
        el = &buffer.data[i];
​
        // 在json对象中添加一个子对象: {}
        zbx_json_addobject(&json, NULL);
        // 在子对象中添加字符串元素 "host": host
        zbx_json_addstring(&json, ZBX_PROTO_TAG_HOST, el->host, ZBX_JSON_TYPE_STRING);
        // 在子对象中添加字符串元素 "key": key.     *** 要发送的监控项的键! ***
        zbx_json_addstring(&json, ZBX_PROTO_TAG_KEY, el->key, ZBX_JSON_TYPE_STRING);
​
        // 在子对象中添加字符串元素 "value": value. *** 这就是我们最终要发送的监控项值了! ***
        if (NULL != el->value)
            zbx_json_addstring(&json, ZBX_PROTO_TAG_VALUE, el->value, ZBX_JSON_TYPE_STRING);
​
        if (ITEM_STATE_NOTSUPPORTED == el->state)
        {
            zbx_json_adduint64(&json, ZBX_PROTO_TAG_STATE, ITEM_STATE_NOTSUPPORTED);
        }
        else
        {
            /* add item meta information only for items in normal state */
            if (0 != (ZBX_METRIC_FLAG_LOG & el->flags))
                zbx_json_adduint64(&json, ZBX_PROTO_TAG_LASTLOGSIZE, el->lastlogsize);
            if (0 != (ZBX_METRIC_FLAG_LOG_LOGRT & el->flags))
                zbx_json_adduint64(&json, ZBX_PROTO_TAG_MTIME, el->mtime);
        }
​
        if (0 != el->timestamp)
            zbx_json_adduint64(&json, ZBX_PROTO_TAG_LOGTIMESTAMP, el->timestamp);
​
        if (NULL != el->source)
            zbx_json_addstring(&json, ZBX_PROTO_TAG_LOGSOURCE, el->source, ZBX_JSON_TYPE_STRING);
​
        if (0 != el->severity)
            zbx_json_adduint64(&json, ZBX_PROTO_TAG_LOGSEVERITY, el->severity);
​
        if (0 != el->logeventid)
            zbx_json_adduint64(&json, ZBX_PROTO_TAG_LOGEVENTID, el->logeventid);
​
        zbx_json_adduint64(&json, ZBX_PROTO_TAG_ID, el->id);
​
        zbx_json_adduint64(&json, ZBX_PROTO_TAG_CLOCK, el->ts.sec);
        zbx_json_adduint64(&json, ZBX_PROTO_TAG_NS, el->ts.ns);
        // 关闭子对象,退回上一级对象
        zbx_json_close(&json);
    }
​
    // 关闭数组对象,退回上一级对象
    zbx_json_close(&json);
​
    // call socket(), bind(), connect()
    if (SUCCEED == (ret = zbx_tcp_connect(&s, CONFIG_SOURCE_IP, host, port, MIN(buffer.count * CONFIG_TIMEOUT, 60),
            configured_tls_connect_mode, tls_arg1, tls_arg2)))
    {
        zbx_timespec(&ts);
        // 在json对象中添加字符串元素 "clock": ts.sec
        zbx_json_adduint64(&json, ZBX_PROTO_TAG_CLOCK, ts.sec);
        // 在json对象中添加字符串元素 "ns": ts.ns
        zbx_json_adduint64(&json, ZBX_PROTO_TAG_NS, ts.ns);
​
        zabbix_log(LOG_LEVEL_DEBUG, "JSON before sending [%s]", json.buffer);
        // 会调用到 zbx_tcp_send_ext()函数, 数据已被序列化到json对象中,通过参数json.buffer传递出去
        if (SUCCEED == (ret = zbx_tcp_send(&s, json.buffer)))
        {
            // call read(),接收server端的响应值
            if (SUCCEED == (ret = zbx_tcp_recv(&s)))
            {
                zabbix_log(LOG_LEVEL_DEBUG, "JSON back [%s]", s.buffer);
                // check_response: 解析server端响应字符串
                if (NULL == s.buffer || SUCCEED != check_response(s.buffer))
                {
                    ret = FAIL;
                    zabbix_log(LOG_LEVEL_DEBUG, "NOT OK");
                }
                else
                    zabbix_log(LOG_LEVEL_DEBUG, "OK");  // 发送请求与接收响应,均成功
            }
            else
                err_send_step = "[recv] ";
        }
        else
            err_send_step = "[send] ";
​
        // call shutdown(), close()
        zbx_tcp_close(&s);
    }
    else
        err_send_step = "[connect] ";
out:
    // 释放json对象内存
    zbx_json_free(&json);
​
    if (SUCCEED == ret)
    {
        // 释放buffer空间
        for (i = 0; i < buffer.count; i++)
        {
            el = &buffer.data[i];
​
            zbx_free(el->host);
            zbx_free(el->key);
            zbx_free(el->value);
            zbx_free(el->source);
        }
        buffer.count = 0;
        buffer.pcount = 0;
        buffer.lastsent = now;
        if (0 != buffer.first_error)
        {
            zabbix_log(LOG_LEVEL_WARNING, "active check data upload to [%s:%hu] is working again",
                    host, port);
            buffer.first_error = 0;
        }
    }
    else
    {
        if (0 == buffer.first_error)
        {
            zabbix_log(LOG_LEVEL_WARNING, "active check data upload to [%s:%hu] started to fail (%s%s)",
                    host, port, err_send_step, zbx_socket_strerror());
            buffer.first_error = now;
        }
        zabbix_log(LOG_LEVEL_DEBUG, "send value error: %s%s", err_send_step, zbx_socket_strerror());
    }
ret:
    zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(ret));
​
    return ret;
}

根据上面的源码分析结果,可得出agent发送的数据的格式如下:

{
    "request": "sender data",
    "session": "xxxxxxxxxxx"
    "data": [
        {
            "host": "Hostname",
            "key": "metric_key1",
            "value": "12345",
        },
        {
            "host": "Hostname",
            "key": "metric_key2",
            "value": "67890",
        },
        ...
    ],
    "clock": 1381482905
    "ns": 000001
}

2、agent发送数据后,会从server端收到响应数据,响应数据的格式如下:

{
    "response": "success",
    "info": "Processed 1 Failed 1 Total 2 Seconds spent..."
}

在响应数据中,response的状态可以是successfailure。响应数据的检测与解析在check_response函数中操作。

static int  check_response(char *response)
{
    struct zbx_json_parse   jp;
    // value,info:存储解析server端返回的响应的字段值
    char            value[MAX_STRING_LEN];
    char            info[MAX_STRING_LEN];
    int         ret;
​
    // 解析server端返回的响应
    ret = zbx_json_open(response, &jp);
​
    // 解析响应中的"response"字段值
    if (SUCCEED == ret)
        ret = zbx_json_value_by_name(&jp, ZBX_PROTO_TAG_RESPONSE, value, sizeof(value));
​
    // 解析的结果与"success"字符串比较看是否相等,如果不等,则表明失败了
    if (SUCCEED == ret && 0 != strcmp(value, ZBX_PROTO_VALUE_SUCCESS))
        ret = FAIL;
    
    // 解析响应中的"info"字段值
    if (SUCCEED == ret && SUCCEED == zbx_json_value_by_name(&jp, ZBX_PROTO_TAG_INFO, info, sizeof(info)))
        zabbix_log(LOG_LEVEL_DEBUG, "info from server: '%s'", info);
​
    return ret;
}

通信协议的实现

zbx_tcp_send()最终会调用到zbx_tcp_send_ext()函数,agent与server间的通信协议的数据序列化是在zbx_tcp_send_ext()函数中实现的。

#define ZBX_TCP_HEADER_DATA "ZBXD"
#define ZBX_TCP_HEADER_LEN  ZBX_CONST_STRLEN(ZBX_TCP_HEADER_DATA)
​
int zbx_tcp_send_ext(zbx_socket_t *s, const char *data, size_t len, unsigned char flags, int timeout)
{
#define ZBX_TLS_MAX_REC_LEN 16384
​
    ssize_t     bytes_sent, written = 0;
    size_t      send_bytes, offset, send_len = len, reserved = 0;
    int     ret = SUCCEED;
    char        *compressed_data = NULL;
    zbx_uint32_t    len32_le;
​
    if (0 != timeout)
        zbx_socket_timeout_set(s, timeout);
​
    // 如果设置了ZBX_TCP_PROTOCOL标志
    if (0 != (flags & ZBX_TCP_PROTOCOL))
    {
        size_t  take_bytes;
        // 通信协议数据序列化的缓存区
        char    header_buf[ZBX_TLS_MAX_REC_LEN];    /* Buffer is allocated on stack with a hope that it   */
                                /* will be short-lived in CPU cache. Static buffer is */
                                /* not used on purpose.                   */
​
        // 如果设置了ZBX_TCP_COMPRESS标志, 则进行数据的压缩
        if (0 != (flags & ZBX_TCP_COMPRESS))
        {
            // 调用zbx_compress函数进行数据压缩,压缩结果存放于compressed_data中
            if (SUCCEED != zbx_compress(data, len, &compressed_data, &send_len))
            {
                zbx_set_socket_strerror("cannot compress data: %s", zbx_compress_strerror());
                ret = FAIL;
                goto cleanup;
            }
​
            data = compressed_data;
            // 设置reserved字段的值为数据压缩前的长度
            reserved = len;
        }
​
        // 序列化协议头,"ZBXD"字符串,
        memcpy(header_buf, ZBX_TCP_HEADER_DATA, ZBX_CONST_STRLEN(ZBX_TCP_HEADER_DATA));
        // header_buf的指针偏移4字节
        offset = ZBX_CONST_STRLEN(ZBX_TCP_HEADER_DATA);
​
        // 写入标志值,指针偏移1个字节
        header_buf[offset++] = flags;
​
        // zbx_htole_uint32:主机序转为little endian字节序
        len32_le = zbx_htole_uint32((zbx_uint32_t)send_len);
        // 写入要发送的数据的长度
        memcpy(header_buf + offset, &len32_le, sizeof(len32_le));
        // 指针偏移4个字节
        offset += sizeof(len32_le);
​
        len32_le = zbx_htole_uint32((zbx_uint32_t)reserved);
        // 写入reserved字段的值
        memcpy(header_buf + offset, &len32_le, sizeof(len32_le));
        // 指针偏移4个字节
        offset += sizeof(len32_le);
​
        // 比较发送数据的长度与ZBX_TLS_MAX_REC_LEN的大小,即每次最多发送16kb(16384/1024)数据   
        take_bytes = MIN(send_len, ZBX_TLS_MAX_REC_LEN - offset);
        // 写入data
        memcpy(header_buf + offset, data, take_bytes);
​
        // send_bytes保存要发送的数据的长度
        send_bytes = offset + take_bytes;
​
        // 发送send_bytes字节的数据,可能会分多次发送,因此这里使用了一个循环
        while (written < (ssize_t)send_bytes)
        {
            // call write(),向server端发送数据
            if (ZBX_PROTO_ERROR == (bytes_sent = zbx_tcp_write(s, header_buf + written,
                    send_bytes - (size_t)written)))
            {
                ret = FAIL;
                goto cleanup;
            }
            written += bytes_sent;
        }
​
        // written中存储已发送的数据的长度,这里减去offset值,因为协议头不包含在数据的范围内。
        written -= offset;
    }
​
    // 如果已发送的数据长度仍小于要发送的数据的长度,则继续执行上面的发送逻辑,每次最多发送16kb数据,继续发送剩余的数据
    while (written < (ssize_t)send_len)
    {
        if (ZBX_TCP_SEC_UNENCRYPTED == s->connection_type)
            send_bytes = send_len - (size_t)written;
        else
            send_bytes = MIN(ZBX_TLS_MAX_REC_LEN, send_len - (size_t)written);
​
        if (ZBX_PROTO_ERROR == (bytes_sent = zbx_tcp_write(s, data + written, send_bytes)))
        {
            ret = FAIL;
            goto cleanup;
        }
        written += bytes_sent;
    }
cleanup:
    // 释放压缩数据的内存
    zbx_free(compressed_data);
​
    if (0 != timeout)
        zbx_socket_timeout_cleanup(s);
​
    return ret;
​
#undef ZBX_TLS_MAX_REC_LEN
}
发布了14 篇原创文章 · 获赞 2 · 访问量 2132

猜你喜欢

转载自blog.csdn.net/mcmoo/article/details/103565196
今日推荐