除了监控报警之外,监控系统的另外一种重要信息是监控数据。监控数据来源于主机的agent模块,agent收集到主机的被监控的数据,实时上报给transfer模块,tansfer模块把信息及时反映给图形界面模块和监控数据判断模块。
1、源码编译transfer模块
1.1搭建环境
安装C语言、golang、mysql、redis、open-falcon环境等过程上一次已经在对alarm模块报警信息处理的文章里记录过了,就不再重复记录了。
1.2编译transfer源码
进入transfer源码目录,命令行输入如下内容回车:
liang@ubuntu:~/goproj/src/github.com/open-falcon/transfer$ ./control build
打印如下信息即编译成功:
0.0.14 190a7eb
使用源代码编译方便以后可以按照自身的需求,自定义transfer的功能,如果没有这样的需求可以直接命令行安装open-falcon,安装完后修改配置文件,启动各个模块即可。
2、监控数据的传递过程
2.1数据的来源
监控数据来自于agent模块,agent模块收集到主机的各项数据后发送到transfer模块,包括:内存使用情况、CPU使用情况、磁盘使用情况、磁盘IO情况、网络使用情况、内核状态、自定义数据等等。
2.2数据的传递方法
transfer模块与agent模块使用rpc的通信方式传递监控数据,agent发送rpc请求,包含:方法、参数。方法是transfer响应rpc请求的函数,参数是上传的监控数据,以metric为单位上传,每一个metric对应一个监控指标。transfer接受rpc请求后,将会给agent返回一个rpc请求响应结果,agent即可得知监控数据是否上传成功。
2.3数据的接收过程
transfer模块注册监听rpc请求的函数,定义该函数的参数为结构体指针数组的形式,那么,agent模块传入的请求方法使用的参数也是要对应为数组的形式。agent模块发送rpc请求后,transfer执行该函数,开始分析传入的参数,最后返回rpc请求结果。
3、监控数据的分析过程
3.1数据的分析过程
open-flacon的官方文档里面可以得知,agent模块上传的监控数据项是以json的格式上传,而且监控数据被保存在一个结构体指针数组里面,每一个监控数据项是一个json格式结构体,一个数组包含多个json结构(即多个监控数据项),transfer拿到rpc请求参数后,遍历该数组,拿到每一数组之后,按照json结构体格式进行分析,并且判断关键项的值是否合法。最后,把每一项合法的json格式监控数据重新组织成数据结构体,再添加到另一个结构体指针里面等待处理。
3.2数据分析原函数
open-falcon transfer模块监控数据上传rpc请求函数原型:
// process new metric values
func RecvMetricValues(args []*cmodel.MetricValue, reply *cmodel.TransferResponse, from string) error {
start := time.Now()
reply.Invalid = 0
items := []*cmodel.MetaData{}
for _, v := range args {
if v == nil {
reply.Invalid += 1
continue
}
// 历史遗留问题.
// 老版本agent上报的metric=kernel.hostname的数据,其取值为string类型,现在已经不支持了;所以,这里硬编码过滤掉
if v.Metric == "kernel.hostname" {
reply.Invalid += 1
continue
}
if v.Metric == "" || v.Endpoint == "" {
reply.Invalid += 1
continue
}
if v.Type != g.COUNTER && v.Type != g.GAUGE && v.Type != g.DERIVE {
reply.Invalid += 1
continue
}
if v.Value == "" {
reply.Invalid += 1
continue
}
if v.Step <= 0 {
reply.Invalid += 1
continue
}
if len(v.Metric)+len(v.Tags) > 510 {
reply.Invalid += 1
continue
}
// TODO 呵呵,这里需要再优雅一点
now := start.Unix()
if v.Timestamp <= 0 || v.Timestamp > now*2 {
v.Timestamp = now
}
fv := &cmodel.MetaData{
Metric: v.Metric,
Endpoint: v.Endpoint,
Timestamp: v.Timestamp,
Step: v.Step,
CounterType: v.Type,
Tags: cutils.DictedTagstring(v.Tags), //TODO tags键值对的个数,要做一下限制
}
valid := true
var vv float64
var err error
switch cv := v.Value.(type) {
case string:
vv, err = strconv.ParseFloat(cv, 64)
if err != nil {
valid = false
}
case float64:
vv = cv
case int64:
vv = float64(cv)
default:
valid = false
}
if !valid {
reply.Invalid += 1
continue
}
fv.Value = vv
items = append(items, fv)
}
// statistics
cnt := int64(len(items))
proc.RecvCnt.IncrBy(cnt)
if from == "rpc" {
proc.RpcRecvCnt.IncrBy(cnt)
} else if from == "http" {
proc.HttpRecvCnt.IncrBy(cnt)
}
cfg := g.Config()
if cfg.Graph.Enabled {
sender.Push2GraphSendQueue(items, cfg.Graph.Migrating)
}
if cfg.Judge.Enabled {
sender.Push2JudgeSendQueue(items)
}
reply.Message = "ok"
reply.Total = len(args)
reply.Latency = (time.Now().UnixNano() - start.UnixNano()) / 1000000
return nil
}
4、使用netcat上传数据
4.1数据的上传格式
rpc请求函数中的参数,该参数使用json格式。
4.2数据的上传命令
使用nc(netcate)网络控制命令完成rpc请求
echo "{\"method\":\"Transfer.Update\",\"params\":[[{\"endpoint\":\"bogon\",\"metric\":\"agent.test\",\"value\":2,\"step\":60,\"counterType\":\"GAUGE\",\"tags\":\"open-falocn\",\"timestamp\":123456789}]]}" | nc 192.168.12.88 8433
这里因为rpc请求的传入参数是结构体指针数组的形式,纠结了一段很长的时间,以后考虑问题一定要全面,既然使用到rpc请求,就要考虑到rpc请求函数的传参问题,参数不对rpc请求肯定失败。
5、使用socket上传数据
5.1数据的上传格式
rpc请求函数中的参数,该参数使用json格式。
"{\"method\":\"Transfer.Update\",\"params\":[[{\"endpoint\":\"bogon\",\"metric\":\"agent.test\",\"value\":2,\"step\":60,\"counterType\":\"GAUGE\",\"tags\":\"open-falocn\",\"timestamp\":123456789}]]}"
5.2数据的上传过程
一个socket客户端,请求连接transfer监听的rpc端口,发送rpc请求数据如上格式,接受rpc请求响应结果,关闭socket。
这里曾经尝试了好多方法:libjrpc、libjson、golang、python、curl、openrpc、等都没有实现。只能说生活状态乱,工作效率也是很低下的。折腾了好长时间,一个简单的socket就把数据发送过去了,真是泪崩,也是没有完全理解当中的原理,一直埋头看jsonrpc上的内容。
这一次又是一个教训!