ceilometer监控源码分析之任务队列

来源:互联网 发布:精易编程助手生成模块 编辑:程序博客网 时间:2024/05/22 07:43

ceilometer监控源码分析之任务队列

场景描述:

ceilometer(这里仅指监控任务)在每个宿主机上运行,读取/etc/ceilometer/pipeline.yaml中配置定时执行监控任务。
pipeline.yaml内容如下:
这里写图片描述
图注:该图中有三个监控项,分别是heartbeat, cpu, memory,interval表示定时间隔。

代码分析:

( 一 )
故事从/ceilometer/agent/base.py 讲起
openstack项目通常的结构是manage+instance。base.py下有两个关键的类,一个是AgentManager,一个是PollingTask.

1. PollingTask
实现Task的类,主要函数是 poll_and_publish (),该函数实现了从获取监控数据至发送数据的全过程。
2. AgentManager
该类继承os_service, 作为进程入口,持有且管理Task,主要函数是 start()

def start(self):        self.pipeline_manager = publish_pipeline.setup_pipeline()        self.partition_coordinator.start()        self.join_partitioning_groups()        # allow time for coordination if necessary        delay_start = self.partition_coordinator.is_active()        # set shuffle time before polling task if necessary        delay_polling_time = random.randint(            0, cfg.CONF.shuffle_time_before_polling_task)        for interval, task in six.iteritems(self.setup_polling_tasks()):            delay_time = (interval + delay_polling_time if delay_start                          else delay_polling_time)            self.tg.add_timer(interval,                              self.interval_task,                              initial_delay=delay_time,                              task=task)        self.tg.add_timer(cfg.CONF.coordination.heartbeat,                          self.partition_coordinator.heartbeat)

代码注释:

  • pipeline_manager从pipeline.yaml文件解析出需要监控的项,封装为interval,task的二元组
  • self.tg 实例化一个线程池,实现在/ceilometer/openstack/common/threadgroup.py
  • self.interval_task 方法是调用每一个task类中的poll_and_publish()
 def poll_and_publish(self):        cache = {}        discovery_cache = {}        for source_name in self.pollster_matches:            with self.publishers[source_name] as publisher:                for pollster in self.pollster_matches[source_name]:                    try:                        samples = list(pollster.obj.get_samples(                            manager=self.manager,                            cache=cache,                            resources=polling_resources                        ))                        publisher(samples)                    except plugin_base.PollsterPermanentError as err:                        LOG.error(_(                            'Prevent pollster %(name)s for '                            'polling source %(source)s anymore!')                            % ({'name': pollster.name, 'source': source_name}))                        self.resources[key].blacklist.append(err.fail_res)

代码注释:

  • 只摘抄了核心代码
  • samples = list() 是调用pollster.obj的get_samples获取每一个vm的监控数据,并返回一个list。
  • publisher() 将获取的监控数据,一起发送。

小结:到目前为止,ceilometer启动服务后,会读取配置拿到需要监控的监控项,然后针对每一项起一个线程去执行定时任务。任务内容是获取宿主机上所有虚拟机监控信息,并发送。

( 二 )
/ceilometer/openstack/common/threadgroup.py
该文件下有两个类,一个是 ThreadGroup,一个是 Thread。

  1. Thread 主要是对greenthread简单的封装,并将threadgroup作为类变量。
  2. ThreadGroup 实例化一个eventlet的greenpool.封装控制pool的常规操作。

上一小结第一个代码片段中,我们通过 self.tg.add_timer() 将每一个pipeline任务加入线程池。

    def add_timer(self, interval, callback, initial_delay=None,                  *args, **kwargs):        pulse = loopingcall.FixedIntervalLoopingCall(callback, *args, **kwargs)        pulse.start(interval=interval,                    initial_delay=initial_delay)        self.timers.append(pulse)

@param: interval 定时任务执行间隔
@param: callback 上一小节分析的poll_and_publish方法
这节代码关键是pulse是个什么?
loopingcall.FixedIntervalLoopingCall里面封装了一个greenthread,将callback作为参数传进去,并生成一个新的协程,其基本方法就是 start(), stop() 和 wait()

         def _inner():            try:                while self._running:                    start = _ts()                    self.f(*self.args, **self.kw)                    end = _ts()                    if not self._running:                        break                    delay = end - start - interval                    if delay > 0:                        LOG.warn(_LW('task %(func_name)r run outlasted '                                     'interval by %(delay).2f sec'),                                 {'func_name': self.f, 'delay': delay})                    greenthread.sleep(-delay if delay < 0 else 0)

代码注释:

  • self.f 还是之前分析的 poll_and_publish(),service的主要 job
  • delay是计算 poll_and_publish() 执行时间差,并减去任务执行间隔时间
  • delay取反后,就是任务需要sleep的准确时间。

小结:至此,就是服务主循环实现的过程。对于多个pipeline,我们启动多个协程,各自计时,实现获取数据及推送功能。

( 三 )
回到 /ceilometer/agent/base.py
我们来看一下如何获取一台宿主机上,所有虚拟机监控数据。在这里我们以 cpu负载为例。
如果忘记下面这段代码可以回顾下第一小节。

    samples = list(pollster.obj.get_samples(        manager=self.manager,        cache=cache,        resources=polling_resources))

我们通过这个得到监控数据的list。也就是 pollster.obj.get_samples() 会返回所有监控数据。

    def get_samples(self, manager, cache, resources):        resources_no_repeat = []        for r in resources:            uniq_id = BaseParallelPollster.get_resource_identity(r)            # avoid re-add task.If not those most time-consuming task will            # occupy all thread, other waiting tasks cannot be attached            # to a thread.            if uniq_id not in BaseParallelPollster.uniq_ids:                BaseParallelPollster.uniq_ids.add(uniq_id)                resources_no_repeat.append(r)        self._collector.add_tasks(            [PoolTask(                self.inspector_resource_info,                args=[r, manager, cache],                callback=self.handle_result, ex_callback=self.handle_exception)                for r in resources_no_repeat])        success_taskes = self._collector.wait_for_result(self._default_timeout)        result = []        for _t in success_taskes:            result += self.convert_info_2_sample(_t.result, *_t.args)        return result.__iter__()

代码注释:

  • resources是通过libvirt接口获取该宿主机上所有vm的instances。
  • self._collector 显然是针对每一个vm实例,将其放入线程池中,获取其监控数据。
  • success_taskes 是拿到所有运行结果。
  • self.convert_info_2_sample() 将数据转换为我们需要的格式。

self._collector.add_tasks() 是在ResultCollector类中,主要方法是 add_tasks , start_exec_tasks 和 wait_for_result。
主要分析 wait_for_result 方法

    def wait_for_result(self, time_out, check_interval=0.1):        assert check_interval > 0        assert check_interval < time_out        #  start all task.        self.start_exec_tasks()        #  wait for all task for result.        time_start = int(time.time())        time.sleep(0.1)        while time.time() - time_start < time_out and not self._is_finished():            time.sleep(check_interval)        return [_t for _t in self._tasks if _t.result]

代码注释:

  • 如果等待执行时间超过 timeout, 则跳出while循环
  • 如果所有结果都完成, 则跳出while循环
  • 只将有数据的对象返回
  • 每一个任务执行完会回调 _inc_decorate 给 self._finished + 1, 如果self._finished 数量大于等于task,则所有任务都完成。

总结

ceiometer监控任务队列框架如上所述,一个大的协程里面套了一个小的协程池。由于python协程存在自己的缺陷,为了满足并发,我们将大的协程拆开为进程,例如ceilometer-agent-compute-cpu, ceilometer-agent-compute-mem 等。

遗留问题:
如果一台宿主机上vm很多,那么再执行的时候,会大量并发调用libvirt接口,会造成libvirt部分锁的问题。从代码分析结果来看,只要ceilometer在interval内获取到数据即可,( sleep 时间是 interval - 函数执行时间)。
那么针对这个问题,我觉得可以限制ceilometer小协程池的数量,让获取监控数据在interval之内,来缓解libvirt锁的问题。

0 0
原创粉丝点击