ceilometer監控源碼分析之任務隊列

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鎖的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章