一、简单介绍ceilometer

这里长话短说, ceilometer是用来采集openstack下面各种资源的在某一时刻的资源值,比如云硬盘的大小等。下面是官网现在的架构图

这里除了ceilometer的架构图,还有另外三个组件:

  • Panko 用来存储事件的, 后面用来实现cloudkitty事件秒级计费也是我的工作之一,目前实现来一部分,有时间单独在写一篇博文。
  • gnocchi是用来存储ceilometer的计量数据,之前的版本是存在mongo中, 不过随着计量数据的不断累计, 查询性能变得极低, 因此openstack后面推出来gnocchi项目,gnocchi的存储后端支持redis,file,ceph等等。这一块也是我负责,目前已经实现了, 有时间也可以写一篇文章。
  • Aodh 是用来告警的。

这里需要注意ceilometer 主要有两个agent:

  • 一个是polling 主要是定时调用相应的openstack接口来获取计量数据,
  • 一个是notification 主要是用来监听openstack的事件消息,然后转换成相应的计量数据,

两者的计量数据, 最后通过定义的pipeline,传递给gnocchi暴露出来的rest API ,后面由gnocchi来做聚合处理以及存储

下面来看一下,具体的官网的数据采集和处理转发的架构图

再来看一下数据的处理过程

这里,我觉得官方文档的架构图描述得非常好, 我就不在多说来。

二、源码分析

  说实话, 目测openstack估计是最大的python项目了,真的是一个庞然大物。第一次接触的时候,完全不知所措。不过看看就强一点了, 虽然有很多地方还是懵逼。看openstack下面的项目的话,其实有些文件很重要比如setup.py, 里面配置了项目的入口点。这篇文章,我主要分析polling这一块是如何实现的, 其他的地方类似。

  • ceilometer polling-agent启动的地方

    # ceilometer/cmd/polling.py
    1 def main():
    conf = cfg.ConfigOpts()
    conf.register_cli_opts(CLI_OPTS)
    service.prepare_service(conf=conf)
    sm = cotyledon.ServiceManager()
    sm.add(create_polling_service, args=(conf,))
    oslo_config_glue.setup(sm, conf)
    sm.run() # 前面几行是读取配置文件, 然后通过cotyledon这个库add一个polling的service,最后run 起来。 cotyledon这个库简单看了一下,可以用来启动进程任务
    def create_polling_service(worker_id, conf):
    return manager.AgentManager(worker_id,
    conf,
    conf.polling_namespaces,
    conf.pollster_list)
    # create_polling_service 返回了一个polling agent polling-namespaces的默认值为choices=['compute', 'central', 'ipmi'],
    
    
  • polling AgentManager # ceilometer/agent/manager.py
 class AgentManager(service_base.PipelineBasedService):

     def __init__(self, worker_id, conf, namespaces=None, pollster_list=None, ):

         namespaces = namespaces or ['compute', 'central']
pollster_list = pollster_list or []
group_prefix = conf.polling.partitioning_group_prefix # features of using coordination and pollster-list are exclusive, and
# cannot be used at one moment to avoid both samples duplication and
# samples being lost
if pollster_list and conf.coordination.backend_url:
raise PollsterListForbidden() super(AgentManager, self).__init__(worker_id, conf) def _match(pollster):
"""Find out if pollster name matches to one of the list."""
return any(fnmatch.fnmatch(pollster.name, pattern) for
pattern in pollster_list) if type(namespaces) is not list:
namespaces = [namespaces] # we'll have default ['compute', 'central'] here if no namespaces will
# be passed
extensions = (self._extensions('poll', namespace, self.conf).extensions
for namespace in namespaces)
# get the extensions from pollster builder
extensions_fb = (self._extensions_from_builder('poll', namespace)
for namespace in namespaces)
if pollster_list:
extensions = (moves.filter(_match, exts)
for exts in extensions)
extensions_fb = (moves.filter(_match, exts)
for exts in extensions_fb) self.extensions = list(itertools.chain(*list(extensions))) + list(
itertools.chain(*list(extensions_fb))) if self.extensions == []:
raise EmptyPollstersList() discoveries = (self._extensions('discover', namespace,
self.conf).extensions
for namespace in namespaces)
self.discoveries = list(itertools.chain(*list(discoveries)))
self.polling_periodics = None self.partition_coordinator = coordination.PartitionCoordinator(
self.conf)
self.heartbeat_timer = utils.create_periodic(
target=self.partition_coordinator.heartbeat,
spacing=self.conf.coordination.heartbeat,
run_immediately=True) # Compose coordination group prefix.
# We'll use namespaces as the basement for this partitioning.
namespace_prefix = '-'.join(sorted(namespaces))
self.group_prefix = ('%s-%s' % (namespace_prefix, group_prefix)
if group_prefix else namespace_prefix) self.notifier = oslo_messaging.Notifier(
messaging.get_transport(self.conf),
driver=self.conf.publisher_notifier.telemetry_driver,
publisher_id="ceilometer.polling") self._keystone = None
self._keystone_last_exception = None def run(self):
super(AgentManager, self).run()
self.polling_manager = pipeline.setup_polling(self.conf)
self.join_partitioning_groups()
self.start_polling_tasks()
self.init_pipeline_refresh() 1 初始化函数里面通过 ExtensionManager加载setup里面定义的各个指标的entry point 包括discover和poll,
discover就是调用openstack的api来get 资源,
poll 就是将discover获取到资源转换成相应的sample(某一时刻的指标值)
2 如果有多个agent 还会创建一个定时器来做心跳检测

3 定义收集到的数据通过消息队列转发送到哪里去 (oslo_messaging.Notifier)

4 之后通过run方法启动polling agent

# setup.py
ceilometer.discover.compute =
local_instances = ceilometer.compute.discovery:InstanceDiscovery

ceilometer.poll.compute =
    disk.read.requests = ceilometer.compute.pollsters.disk:ReadRequestsPollster
disk.write.requests = ceilometer.compute.pollsters.disk:WriteRequestsPollster
disk.read.bytes = ceilometer.compute.pollsters.disk:ReadBytesPollster
disk.write.bytes = ceilometer.compute.pollsters.disk:WriteBytesPollster
disk.read.requests.rate = ceilometer.compute.pollsters.disk:ReadRequestsRatePollster
  ...... 
  • 设置 polling 比如多长的时间间隔去获取资源的指标

     def setup_polling(conf):
    """Setup polling manager according to yaml config file."""
    cfg_file = conf.polling.cfg_file
    return PollingManager(conf, cfg_file)
    class PollingManager(ConfigManagerBase):
    """Polling Manager Polling manager sets up polling according to config file.
    """ def __init__(self, conf, cfg_file):
    """Setup the polling according to config. The configuration is supported as follows: {"sources": [{"name": source_1,
    "interval": interval_time,
    "meters" : ["meter_1", "meter_2"],
    "resources": ["resource_uri1", "resource_uri2"],
    },
    {"name": source_2,
    "interval": interval_time,
    "meters" : ["meter_3"],
    },
    ]}
    } The interval determines the cadence of sample polling Valid meter format is '*', '!meter_name', or 'meter_name'.
    '*' is wildcard symbol means any meters; '!meter_name' means
    "meter_name" will be excluded; 'meter_name' means 'meter_name'
    will be included. Valid meters definition is all "included meter names", all
    "excluded meter names", wildcard and "excluded meter names", or
    only wildcard. The resources is list of URI indicating the resources from where
    the meters should be polled. It's optional and it's up to the
    specific pollster to decide how to use it. """
    super(PollingManager, self).__init__(conf)
    try:
    cfg = self.load_config(cfg_file)
    except (TypeError, IOError):
    LOG.warning(_LW('Unable to locate polling configuration, falling '
    'back to pipeline configuration.'))
    cfg = self.load_config(conf.pipeline_cfg_file)
    self.sources = []
    if 'sources' not in cfg:
    raise PollingException("sources required", cfg)
    for s in cfg.get('sources'):
    self.sources.append(PollingSource(s)) # 根据下面的配置文件 etc/ceilometer/polling.yaml 初始化配置
    ---
    sources:
    - name: all_pollsters
    interval: 600
    meters:
    - "*"
  • 将每个discovery根据相应的group id 加入到同一个组里面去
         def join_partitioning_groups(self):
    self.groups = set([self.construct_group_id(d.obj.group_id)
    for d in self.discoveries])
    # let each set of statically-defined resources have its own group
    static_resource_groups = set([
    self.construct_group_id(utils.hash_of_set(p.resources))
    for p in self.polling_manager.sources
    if p.resources
    ])
    self.groups.update(static_resource_groups) if not self.groups and self.partition_coordinator.is_active():
    self.partition_coordinator.stop()
    self.heartbeat_timer.stop() if self.groups and not self.partition_coordinator.is_active():
    self.partition_coordinator.start()
    utils.spawn_thread(self.heartbeat_timer.start) for group in self.groups:
    self.partition_coordinator.join_group(group)
  • 开启polling task
         def start_polling_tasks(self):
    # 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, self.conf.shuffle_time_before_polling_task) data = self.setup_polling_tasks() # Don't start useless threads if no task will run
    if not data:
    return # One thread per polling tasks is enough
    self.polling_periodics = periodics.PeriodicWorker.create(
    [], executor_factory=lambda:
    futures.ThreadPoolExecutor(max_workers=len(data))) for interval, polling_task in data.items():
    delay_time = (interval + delay_polling_time if delay_start
    else delay_polling_time) @periodics.periodic(spacing=interval, run_immediately=False)
    def task(running_task):
    self.interval_task(running_task) utils.spawn_thread(utils.delayed, delay_time,
    self.polling_periodics.add, task, polling_task) utils.spawn_thread(self.polling_periodics.start, allow_empty=True)
    # 根据之前的polling.yaml和从setup文件动态加载的extensions生成一个个task
    def setup_polling_tasks(self):
    polling_tasks = {}
    for source in self.polling_manager.sources:
    polling_task = None
    for pollster in self.extensions:
    if source.support_meter(pollster.name):
    polling_task = polling_tasks.get(source.get_interval())
    if not polling_task:
    polling_task = self.create_polling_task()
    polling_tasks[source.get_interval()] = polling_task
    polling_task.add(pollster, source)
    return polling_tasks 之后通过periodics 和polling.yaml定义的间隔周期性的执行任务
    def interval_task(self, task):
    # NOTE(sileht): remove the previous keystone client
    # and exception to get a new one in this polling cycle.
    self._keystone = None
    self._keystone_last_exception = None task.poll_and_notify()
    def poll_and_notify(self):
    """Polling sample and notify."""
    cache = {}
    discovery_cache = {}
    poll_history = {}
    for source_name in self.pollster_matches:
    for pollster in self.pollster_matches[source_name]:
    key = Resources.key(source_name, pollster)
    candidate_res = list(
    self.resources[key].get(discovery_cache))
    if not candidate_res and pollster.obj.default_discovery:
    candidate_res = self.manager.discover(
    [pollster.obj.default_discovery], discovery_cache) # Remove duplicated resources and black resources. Using
    # set() requires well defined __hash__ for each resource.
    # Since __eq__ is defined, 'not in' is safe here.
    polling_resources = []
    black_res = self.resources[key].blacklist
    history = poll_history.get(pollster.name, [])
    for x in candidate_res:
    if x not in history:
    history.append(x)
    if x not in black_res:
    polling_resources.append(x)
    poll_history[pollster.name] = history # If no resources, skip for this pollster
    if not polling_resources:
    p_context = 'new ' if history else ''
    LOG.info(_LI("Skip pollster %(name)s, no %(p_context)s"
    "resources found this cycle"),
    {'name': pollster.name, 'p_context': p_context})
    continue LOG.info(_LI("Polling pollster %(poll)s in the context of "
    "%(src)s"),
    dict(poll=pollster.name, src=source_name))
    try:
    polling_timestamp = timeutils.utcnow().isoformat()
    samples = pollster.obj.get_samples(
    manager=self.manager,
    cache=cache,
    resources=polling_resources
    )
    sample_batch = [] for sample in samples:
    # Note(yuywz): Unify the timestamp of polled samples
    sample.set_timestamp(polling_timestamp)
    sample_dict = (
    publisher_utils.meter_message_from_counter(
    sample, self._telemetry_secret
    ))
    if self._batch:
    sample_batch.append(sample_dict)
    else:
    self._send_notification([sample_dict]) if sample_batch:
    self._send_notification(sample_batch) except plugin_base.PollsterPermanentError as err:
    LOG.error(_LE(
    'Prevent pollster %(name)s from '
    'polling %(res_list)s on source %(source)s anymore!')
    % ({'name': pollster.name, 'source': source_name,
    'res_list': err.fail_res_list}))
    self.resources[key].blacklist.extend(err.fail_res_list)
    except Exception as err:
    LOG.error(_LE(
    'Continue after error from %(name)s: %(error)s')
    % ({'name': pollster.name, 'error': err}),
    exc_info=True)
    # 循环调用discovery的extensions的 discover方法,获取资源, 之后调用polling的extensions的get_samples方法将资源转换成相应的指标对象sample
    之后将消息发送到消息队列里面去。然后由ceilometer的notification agnet 获取,之后在做进一步的转换发送给gnocchi

polling agent 的基本过程就是这样的,后面就是notification agent 的处理

ceilometer 源码分析(polling)(O版)的更多相关文章

  1. 一步步实现windows版ijkplayer系列文章之六——SDL2源码分析之OpenGL ES在windows上的渲染过程

    一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...

  2. 一步步实现windows版ijkplayer系列文章之三——Ijkplayer播放器源码分析之音视频输出——音频篇

    一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...

  3. 一步步实现windows版ijkplayer系列文章之二——Ijkplayer播放器源码分析之音视频输出——视频篇

    一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...

  4. JVM源码分析之警惕存在内存泄漏风险的FinalReference(增强版)

    概述 JAVA对象引用体系除了强引用之外,出于对性能.可扩展性等方面考虑还特地实现了四种其他引用:SoftReference.WeakReference.PhantomReference.FinalR ...

  5. 一个由正则表达式引发的血案 vs2017使用rdlc实现批量打印 vs2017使用rdlc [asp.net core 源码分析] 01 - Session SignalR sql for xml path用法 MemCahe C# 操作Excel图形——绘制、读取、隐藏、删除图形 IOC,DIP,DI,IoC容器

    1. 血案由来 近期我在为Lazada卖家中心做一个自助注册的项目,其中的shop name校验规则较为复杂,要求:1. 英文字母大小写2. 数字3. 越南文4. 一些特殊字符,如“&”,“- ...

  6. Duilib源码分析(三)XML解析器—CMarkup

    上一节介绍了控件构造器CDialogBuilder,接下来将分析其XML解析器CMarkup: CMarkup:xml解析器,目前内置支持三种编码格式:UTF8.UNICODE.ASNI,默认为UTF ...

  7. YARN DistributedShell源码分析与修改

    YARN DistributedShell源码分析与修改 YARN版本:2.6.0 转载请注明出处:http://www.cnblogs.com/BYRans/ 1 概述 2 YARN Distrib ...

  8. jquery2源码分析系列

    学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...

  9. [源码]String StringBuffer StringBudlider(2)StringBuffer StringBuilder源码分析

      纵骑横飞 章仕烜   昨天比较忙 今天把StringBuffer StringBulider的源码分析 献上   在讲 StringBuffer StringBuilder 之前 ,我们先看一下 ...

随机推荐

  1. ethers.js-6-Low-Level API

    Low-Level API These are advanced, low-level API features that should, for most people not be necessa ...

  2. koa2怎么自定义一个中间件

    首先定义一个方法 function test(ctx){ global.console.log('m1') } 把这个中间件导出去 module.exports=function(){ return ...

  3. [原创]C/C++语言中,如何在main.c或main.cpp中调用另一个.c文件

    C/C++语言中,如何在main.cpp中调用另一个.c文件主要有5种思路: 1.在VS2012 IDE中,将被引用的.c文件后缀名全部修改为.h,然后通过IDE的解决方案资源管理器中鼠标右键单击“头 ...

  4. HBase的简单java操作

    官方文档:http://hbase.apache.org/book.html java简单操作hbase的表 import org.apache.hadoop.conf.Configuration; ...

  5. httpclient,java跨系统调用,第三接口调用实例

    java跨系统调用,第三方接口调用,有三种方式 1:rmi/rpc 传序列化对象 2:webservice 传xml 3:restful 传json 接下来给大家演示怎么用httpclient调用re ...

  6. Linux服务-http

    目录 1. httpd简介 2. httpd版本 2.1 httpd的特性 2.2 httpd-2.4新增的模块 3. httpd基础 3.1 httpd自带的工具程序 3.2 rpm包安装的http ...

  7. Linux--find命令和 xargs命令组合

    find 查找文件的命令,并可以做出相应的处理 命令格式: find filename [选项][-print -exec -ok ...] 选项参数: 1.-name :按照文件名称查找,可以提前c ...

  8. AWVS使用手册

    目录: 0×00.什么是Acunetix Web Vulnarability Scanner ( What is AWVS?) 0×01.AWVS安装过程.主要文件介绍.界面简介.主要操作区域简介(I ...

  9. Ubuntu 16.04 下简单安装使用golang之备忘

      刚开始学习Go语言,这里记录下我在Ubuntu 16.04下安装使用golang的过程,方便以后查询.   一.安装   1.添加源   如果使用默认的源安装golang的话,版本太低,只到1.6 ...

  10. 基于R语言的结构方程:lavaan简明教程 [中文翻译版]

    lavaan简明教程 [中文翻译版] 译者注:此文档原作者为比利时Ghent大学的Yves Rosseel博士,lavaan亦为其开发,完全开源.免费.我在学习的时候顺手翻译了一下,向Yves的开源精 ...