花了两三天时间把collectd源码大致读了一遍,总的来说还算轻松愉快,callback函数有点多,断点打的有点烦。这里简单总结一下自己觉得有趣和可以学习的地方。
Collectd完全用c编写,主进程性能好,支持无数插件,配置简单,支持其他语言定制插件,
就算现有的插件不能满足你的需求定制一个自己的插件也很简单。
collectd本身的代码并不多,我觉得价值最大的地方还是在其扩展性.
读代码的过程中,有几点让我印象深刻的,一个是如何做到用一套接口去配适所有的插件只需要再配置文件中加上Loadplugin的选项而不用重新编译。
其实方法很简单,
主要代码在int plugin_load(char const *plugin_name, uint32_t flags) 中,
先检查这个插件有没有已经被load过了,所有插件编译生成的动态库都放在指定目录/usr/lib64/collectd下,从配置中我们知道了要加载的插件的名字,
当然这个插件生成的动态库的名字就是*.so,这样我们就知道了要加载的文件.
最后在plugin_load_file中
(reg_handle = (void (*)(void))lt_dlsym(dlh, "module_register"))
得到module_register的函数指针,执行
(*reg_handle)();
去调用执行这个注册函数,这样就进入到了插件中了.
比如在cpu这个插件中module_register是长这样的.
void module_register(void) {
plugin_register_init("cpu", init);
plugin_register_config("cpu", cpu_config, config_keys, config_keys_num);
plugin_register_read("cpu", cpu_read);
} /* void module_register */
int plugin_register_init(const char *name, int (*callback)(void)) {
return (create_register_callback(&list_init, name, (void *)callback,
/* user_data = */ NULL));
} /* plugin_register_init */
初始化插件的函数,只是把注册的函数指针加到list_init队列中,在之后的 plugin_init_all中会统一进行初始化.
le = llist_head(list_init);
while (le != NULL) {
callback_func_t *cf;
plugin_init_cb callback;
plugin_ctx_t old_ctx;
cf = le->value;
old_ctx = plugin_set_ctx(cf->cf_ctx);
callback = cf->cf_callback;
status = (*callback)();
plugin_set_ctx(old_ctx);
这里是在创建read和write thread之前,我们也可以把注册read callback的函数写在init中.
另外collectd最主要的特征就是除了主守护线程之外还有分出来的默认的5个read thread和write thread.这些线程之间使用write_queue_s来传递数据.
我们都知道collectd的插件module_register中,例如cpu插件中有
plugin_register_read(“cpu”, cpu_read);
这个插件就被注册为了一个read的插件,在方法中使用plugin_dispatch_values(&vl);
就能把内容推送到write_queue_s中,交给write的插件进行处理,比如说write_redis,就能把获取的数据存入到redis中了.
配置文件中
# Limit the size of the write queue. Default is no limit. Setting up a limit is
# recommended for servers handling a high volume of traffic.
#WriteQueueLimitHigh 1000000
#WriteQueueLimitLow 800000
就是用来限制这个队列的长度的.
先是write thread,启动的plugin_write_thread中进入loop
value_list_t *vl = plugin_write_dequeue();
等待队列中出现值,再进行处理.
而在plugin_read_thread中collectd创建了一个read_heap,之前注册的read函数都会放到这个heap中进行循环,取出执行再用c_heap_insert()重新放入heap中.
此外代码中通篇都是使用(plugin_read_cb)(user_data_t )之类的函数指针,这些函数指针会先被保存在结构体里管理起来,到使用的时候在重新附给指针变量使用.
颇有面对对象的感觉,也算是学习到了一种c语言写代码的风格.
在补充下collectd中自己定义metric和过滤规则
struct value_list_s {
value_t *values;
size_t values_len;
cdtime_t time;
cdtime_t interval;
char host[DATA_MAX_NAME_LEN];
char plugin[DATA_MAX_NAME_LEN];
char plugin_instance[DATA_MAX_NAME_LEN];
char type[DATA_MAX_NAME_LEN];
char type_instance[DATA_MAX_NAME_LEN];
meta_data_t *meta;
};
typedef struct value_list_s value_list_t;
disk_io_win disk_read:DERIVE:1:U, disk_write:DERIVE:0:U, through_read:DERIVE:0:U, put_write:DERIVE:0:U
对每个数据集说明,type.db文件都包含了一行。每行由两个字段组成,由空格或者tab分隔。第一个字段定义了数据集的名称,第二个字段定义了数据源说明的列表,以空格分隔,对每一个列表单元都以逗号分隔。
数据源说明的格式受到RRDtool的数据源说明格式影响。每个数据源由4部分组成,分别为数据源名,类型,最小值和最大值,之间由冒号分隔。
ds-name:ds-type:min:max。其中ds-type包含4中类型,ABSOULUTE,COUNTER,DERIVE或者GAUSE。mix和max定义了固定值范围。如果U在min或者max中指定,则意味着不知道范围。
至于转发规则请看官方postcache和precache chain,介绍的比较详细。这里给个写的例子
# The above configuration example will ignore all values where the plugin field is "vs_guest" or "vs_host",
# also can addthe type and the type instance .
# All other values will be sent to the write plugin via the default target of the chain.
# # Since this chain is run after the value has been added to the cache, the MySQL show_* command statistics will be available via the unixsock plugin.
LoadPlugin match_regex
<Chain "PostCache">
<Rule "ignore_vsgust">
<Match "regex">
Plugin "^(vs_guest|vs_host)$"
</Match>
<Target "write">
# # If matched: goto write_redis or network determeing on controller or compute node
Plugin "write_redis"
# Plugin "network"
</Target>
</Rule>
<Target "write">
# If not matched: goto Python.writer
Plugin "Python.sql_writer"
</Target>
</Chain>