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_HAVEPARAMS
或0
(取决于监控项是否接受参数)。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(¶meters_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, ¶meters, 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
的状态可以是success
或failure
。响应数据的检测与解析在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
}