2021SC@SDUSC BRPC代码分析(五) —— bvar-Sampler及其相关类详解

2021SC@SDUSC


一、Sampler类简介

        这篇文章我来介绍一下bvar一个很重要的基础组件Sampler,中文翻译成采集器,像Window和PerSecond等和时间有关的都需要使用Sampler来定时采集,每一个需要采集的bvar对应一个Sampler,Sampler相关代码在sampler.h和sampler.cpp里,主要包括四个类,下文将具体分析,包括了Sampler类和它用到的相关类和衍生类的内容。

二、代码分析

首先看结构体Sample,它用来表示采样到的值,有数据值和时间两个属性。
在这里插入图片描述

然后看关键类Sampler,也是一个提升效率的奇异递归模板模式,在上篇文章中同样见过。因为宏观来看上Sampler都是串联起来由一个线程统一调度的,所以要用到链表,需要继承。
两个属性
_used:该Sampler是否被使用。
_mutex:解决销毁和采样的同步问题。
Sampler除构造析构函数外总共包括三个成员函数和一个友元类SamplerCollector,友元类后面马上会深入介绍,先看下这三个函数。
首先采样take_sample()是虚函数,没有实现而是等待继承他的类去实现。
在这里插入图片描述
然后是schedule()函数,它的作用是把当前Sampler全局注册以实现周期性地调用take_sample()。
这个函数的内容很简单,就是调用get_leaky_singleton<>()(函数的实现在butil/memory/singleton_on_pthread_once.h,感兴趣可以自行阅读)获得一个全局的单例后调用<<操作符将this指针写进这个单例的TLS空间。
在这里插入图片描述
最后是destroy()函数,销毁这个Sampler,停止采样,实际上是将_used赋值为false,这会导致在SamplerCollector在执行run()的时候会将其移出链表。
在这里插入图片描述
Sampler基类介绍完后,我们接下来看SamplerCollector类。这个类是一个用来遍历所有的Sampler并执行采样操作的全局结构。可以看到这个继承也很有意思,它继承了一个由Sampler指针和CombineSampler运算实现的Reducer。为什么有这种继承呢,对于需要定期执行的操作,一种常见的做法是使用标准库模块threading提供的类对象Timer,用于表示定时器线程,但随着Sampler的添加,这种做法的效率不高。
内部函数提供了线程的创建,fork等方法。还对fork()做了特别支持。如
单例在fork之前可以为null,子回调将不会注册。
如果在fork之前单例不为null,则将注册子回调并重新创建采样线程。
fork得到的程序可以再次fork。
在这里插入图片描述
首先看一下继承的Reducer使用的CombineSampler,是一个简单的链表合并,把s1插到s2之前。
在这里插入图片描述
在上面SamplerCollector的构造函数里,最主要的就是调用create_sampling_thread()创造一个采样的线程。register_atfork表示是否被注册过,注册子回调并重新创建采样线程。
在这里插入图片描述
上面创建的核心在sampling_thread,它调用了run函数。
在这里插入图片描述
阅读下run函数的代码。run也是执行采样的核心代码。
在这里插入图片描述
Root是整个Collector的根节点,就是链表串联起来的一系列Sampler的头结点,consecutive_nosleep是保存连续没有sleep的次数,设定是每隔一秒采集一次,如果在一秒内采集完,那么会计算出用了多长时间再sleep休眠满一秒钟再进行采样,如果一秒内都没有执行完所有的Sampler这个值就会自增,表示采样过程是busy的,产生了延迟,PassiveStatus类型的cumulated_time和使用它的usage则是用来监控执行时间的。
整个采样是一个死循环,只要_stop不为true就一直执行定时采集任务,采样本身的过程很简单,就是遍历链表,调用每个Sampler的take_sample函数去采样即可。另外再看一些细节。
首先根据CombineSampler运算符不难理解,就是把所有的Sampler串联起来返回后并重置为NULL,随后将这些Sampler指针插入到root节点后面,其实目的就是每次采样前会将新增的Sampler加到队列里。
然后是遍历采样的过程注意会有加锁操作,因为需要和Sampler其他的诸如destroy的函数互斥,然后判断它的_used,如果为false,说明已经执行了destroy,就要从链表中将其移除并delete,否则采样后解锁。在这里插入图片描述
在这里插入图片描述
最后我们再看Sampler的衍生类,ReducerSampler。这是为了满足Reducer类对于Window类的需要。
Window获得之前一段时间内的统计值。不能独立存在,必须依赖于一个已有的计数器。Window会自动更新,不用给它发送数据。出于性能考虑,Window的数据来自于每秒一次对原计数器的采样,在最差情况下,Window的返回值有1秒的延时。
我们分析一下这个类。
首先一共有三个属性。
_reducer:sampler对应reducer的指针.
_q:保存采样结果的队列。
_window_size:当前Sampler的窗口的尺寸,在采样过程中会根据_window_size来调整_q这一有限队列的大小。
在这里插入图片描述
然后来看它的函数。
首先看构造函数。
参数是reducer的指针,赋值给_reducer,_window_size初始值为1,该值可以通过函数改变,构造函数会调用一次采样函数。在开始时调用take_sample,因此不会忽略第一秒的值。
在这里插入图片描述

然后是take_sample()采样函数。该函数首先会判断队列容量是否小于_window_size,如果是则扩充队列大小,实现上是新建一个BoundedQueue将其交换。然后进行采样,根据操作符是否可以逆向来确定调用的Reducer函数,如果butil::is_same<InvOp, VoidOp>::value为true表明没有InvOp,也就是不能逆向,比如Maxer这种Reducer,此类Reducer只能每次保存完值重置,取值的时候也需要汇总时段内的所有Sampler才能得到值,而有InvOp的,比如Adder,每次只记录当前值就行,不需要重置,取值的时候只需首尾减一下即可。最后将sampler存入队列,elim_push在push前会检测是否full,如果是会pop掉队首。
在这里插入图片描述
在这里插入图片描述
接下来是get_value,用于获取某个时间点到现在的采样值。也是根据上面提到的是否能够逆向操作采用不同的方式得到时间点对应的值,比如window_size=5,调用该函数,那么对于Maxer,就是5秒内的最大值,需要使用5秒内的所有采样取出汇总到一块进行比较得到,而对于Adder,是5秒内的累加值,只需要将最近的值减去5秒前的值即可。
在这里插入图片描述
在这里插入图片描述
还有设置window_size的函数,可以看到它只能支持值更大的改变。
在这里插入图片描述
还有get_samples函数,取得从某个时间点开始的所有Sample。和get_value中的取操作是一样的,不再赘述。
在这里插入图片描述


总结

以上就是今天介绍的全部内容,本文进行了bvar内Sampler及其相关类的功能说明和详细源码分析,之后的博客会继续进行代码的分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值