bvar C++
bvar C++
bvar C++ 快速入门。
快速入门
bvar 的基本用法很简单:
#include <bvar/bvar.h>
namespace foo {
namespace bar {
// bvar::Adder<T> used for running sum, we define a Adder for read_error as below
bvar::Adder<int> g_read_error;
// put another bvar inside window so that we can get the value over this period of time
bvar::Window<bvar::Adder<int> > g_read_error_minute("foo_bar", "read_error", &g_read_error, 60);
// ^ ^ ^
// prefix1 monitor name time window, 10 by default
// bvar::LatencyRecorder is a compound varibale, can be used for troughput、qps、avg latency, latency percentile, max latency。
bvar::LatencyRecorder g_write_latency("foo_bar", "write");
// ^ ^
// prefix1 monitor entry, LatencyRecorder includes different bvar, and expose() will add the suffix for them by default, such as write_qps, write_latency etc
// define a varible for the # of 'been-pushed task'
bvar::Adder<int> g_task_pushed("foo_bar", "task_pushed");
// put nested bvar into PerSecond so that we can get the value per second within this time window. Over here what we get is the # of tasks pushed per second
bvar::PerSecond<bvar::Adder<int> > g_task_pushed_second("foo_bar", "task_pushed_second", &g_task_pushed);
// ^ ^
// different from Window, PerSecond will be divided by the time winodw. time window is the last param, we omit here, its 10 by default
} // bar
} // foo我们如何使用 bvar
// run into read errors
foo::bar::g_read_error << 1;
// record down the latenct, which is 23ms
foo::bar::g_write_latency << 23;
// one task has been pushed
foo::bar::g_task_pushed << 1;请记住 Window<> 和 PerSecond<> 是派生变量,我们不需要向它们推送值,它们会自动更新。显然,bvar 既可以作为局部变量,也可以作为成员变量。
本质上常用 的 bvar 类有 7 个,它们都继承自基类 bvar::Variable。
bvar::Adder<T>:计数器,默认值为 0,varname << N等价于varname += N。bvar::Maxer<T>:获取最大值,默认值为std::numeric_limits::min(),varname << N等价于varname = max(varname, N)。bvar::Miner<T>:获取最小值,默认值为std::numeric_limits::max(),varname << N等价于varname = min(varname, N)。bvar::IntRecorder:获取自使用以来的平均值,注意这里我们没有说“一段时间内”,因为这个 bvar 总是配合 Window<> 一起使用,用于计算预定义时间窗口内的平均值。bvar::Window<VAR>:获取时间窗口内的累积和。Window 继承自其他已存在的 bvar,并且会自动更新。bvar::PerSecond<VAR>:获取预定义时间段内每秒的值。PerSecond 同样会自动更新,并且继承自其他 bvar。bvar::LatencyRecorder:用于记录延迟和 QPS,当我们向其中推送延迟值时,可以一次性获得平均延迟、最大延迟和 QPS。
注意事项:请确保 bvar 的名称全局唯一,否则 expose() 将失败。当选项 -bvar_abort_on_same_name 为 true(默认为 false)时,程序将会中止。
命名最佳实践:
不同模块中的 bvar 各不相同,为了避免名称重复,我们最好遵循 module_class_indicator 这一规则:
- module 通常指程序名,可以是产品线的缩写,例如 inf_ds、ecom_retrbs 等。
- class 通常指类名或函数名,例如 storage_manager、file_transfer、rank_stage1。
- indicator 通常指 qps、count、latency 等,以下是一些合法的命名示例。
iobuf_block_count : 29 # module=iobuf class=block indicator=count
iobuf_block_memory : 237568 # module=iobuf class=block indicator=memory
process_memory_resident : 34709504 # module=process class=memory indicator=resident
process_memory_shared : 6844416 # module=process class=memory indicator=shared
rpc_channel_connection_count : 0 # module=rpc class=channel_connection indicator=count
rpc_controller_count : 1 # module=rpc class=controller indicator=count
rpc_socket_count : 6 # module=rpc class=socket indicator=countbvar 会对变量名进行规范化处理,无论我们输入的是 foo::BarNum、foo.bar.num、foo bar num 还是 foo-bar-num,它们最终都会变成 foo\_bar\_num。关于指标的几点说明:
- 数字类指标使用
_count作为后缀,例如request\_count、error\_count - 每秒数字类指标使用
_second作为后缀就足够清晰了,无需使用'\_count\_second'或'\_per\_second',例如request\_second、process\_inblocks\_second - 每分钟数字类指标使用
_minute作为后缀,例如request\_minute、process\_inblocks\_minute;如果需要使用在其他文件中定义的计数器,就必须在头文件中声明该变量
namespace foo {
namespace bar {
// notice g_read_error_minute and g_task_pushed_second are derived bvar, will auto update, no need to declare
extern bvar::Adder<int> g_read_error;
extern bvar::LatencyRecorder g_write_latency;
extern bvar::Adder<int> g_task_pushed;
} // bar
} // foo不要在多个文件中定义全局 Window<> 和 PerSecond<>。不同编译单元中全局变量的初始化顺序是未定义的。 foo.cpp 中定义了 Adder<int> foo_count,那么在 foo_qps.cpp 中再定义 PerSecond<Adder<int> > foo_qps(&foo_count); 就是非法的。
关于线程安全:
- bvar 是线程兼容的。我们可以在不同的线程中操作 bvar,例如可以在多个线程中同时暴露或隐藏不同的 bvar,它们会安全地对全局共享变量进行操作。
- 除了读写 API 之外,bvar 的其他任何函数都不是线程安全的:不能在不同线程中同时暴露或隐藏同一个 bvar,否则可能导致程序崩溃。一般来说,除了读写操作之外,我们不必并发调用任何其他 API。计时可以使用 butil::Timer,其 API 如下:
#include <butil/time.h>
namespace butil {
class Timer {
public:
enum TimerType { STARTED };
Timer();
// butil::Timer tm(butil::Timer::STARTED); // tm is already started after creation.
explicit Timer(TimerType);
// Start this timer
void start();
// Stop this timer
void stop();
// Get the elapse from start() to stop().
int64_t n_elapsed() const; // in nanoseconds
int64_t u_elapsed() const; // in microseconds
int64_t m_elapsed() const; // in milliseconds
int64_t s_elapsed() const; // in seconds
};
} // namespace butilbvar:
Variable 是所有 bvar 的基类,它提供了注册、列举和查找功能。
当用户使用默认参数创建一个 bvar 时,它并没有被注册到任何全局结构中,而只是一个更快的计数器,这意味着我们无法在别处使用它。把这个 bvar 放入全局注册表的操作称为 expose,可以通过调用 expose() 来实现。
全局暴露的 bvar 的名称由 “name” 或 “name+prefix” 组成,可以通过带有 _exposed 后缀的函数进行查找,例如 Variable::describe_exposed("foo") 会返回名为 'foo' 的 bvar 的描述。
如果该名称已经存在,expose() 会打印 FATAL 日志并返回 -1。当选项 -bvar_abort_on_same_name 为 true(默认为 false)时,程序将终止。
expose() 的一些示例如下。
bvar::Adder<int> count1; // create a bvar with defalut params
count1 << 10 << 20 << 30; // values add up to 60.
count1.expose("count1"); // expose the variable globally
CHECK_EQ("60", bvar::Variable::describe_exposed("count1"));
count1.expose("another_name_for_count1"); // expose the variable with another name
CHECK_EQ("", bvar::Variable::describe_exposed("count1"));
CHECK_EQ("60", bvar::Variable::describe_exposed("another_name_for_count1"));
bvar::Adder<int> count2("count2"); // exposed in constructor directly
CHECK_EQ("0", bvar::Variable::describe_exposed("count2")); // default value of Adder<int> is 0
bvar::Status<std::string> status1("count2", "hello"); // the name conflicts. if -bvar_abort_on_same_name is true,
// program aborts, otherwise a fatal log is printed.为避免重名,bvar 需要带有前缀,我们建议的命名方式为 <namespace>_<module>_<name>
为了方便使用,我们提供了 expose_as(),它接受一个前缀参数。
// Expose this variable with a prefix.
// Example:
// namespace foo {
// namespace bar {
// class ApplePie {
// ApplePie() {
// // foo_bar_apple_pie_error
// _error.expose_as("foo_bar_apple_pie", "error");
// }
// private:
// bvar::Adder<int> _error;
// };
// } // foo
// } // bar
int expose_as(const butil::StringPiece& prefix, const butil::StringPiece& name);导出所有变量
导出的常见需求包括通过 HTTP API 查询和写入本地文件。前者由 brpc 的 /vars](https://github.com/apache/brpc/blob/master/docs/cn/vars.md) 服务提供,后者已在 bvar 中实现,默认处于关闭状态。有几种方法可以启用该功能:
使用 gflags 解析输入参数。可以在程序启动时添加
-bvar_dump,也可以在启动后通过 brpc 的/flags](https://github.com/apache/brpc/blob/master/docs/cn/flags.md)服务动态修改参数。gflags 的解析方式如下#include <gflags/gflags.h> ... int main(int argc, char* argv[]) { if (google::SetCommandLineOption("bvar_dump", "true").empty()) { LOG(FATAL) << "Fail to enable bvar dump"; } ... }如果不想使用 gflags,而是希望程序默认开启它们
#include <gflags/gflags.h> ... int main(int argc, char* argv[]) { if (google::SetCommandLineOption("bvar_dump", "true").empty()) { LOG(FATAL) << "Fail to enable bvar dump"; } ... }dump 功能由以下 gflags 控制
名称 默认值 作用 bvar_dump false 创建一个后台线程定期 dump 所有 bvar;当该开关关闭时,所有 bvar_dump_* 开关均不生效 bvar_dump_exclude "" dump 时排除匹配这些通配符的 bvar(以逗号分隔),为空表示不排除任何 bvar bvar_dump_file monitor/bvar.<app>.data 将 bvar dump 到该文件中 bvar_dump_include "" dump 匹配这些通配符的 bvar(以逗号分隔),为空表示包含全部 bvar_dump_interval 10 两次相邻 dump 之间的间隔秒数 bvar_dump_prefix <app> 每个被 dump 的名称都以此前缀开头 bvar_dump_tabs <查看源码> 根据过滤器将 bvar dump 到不同的标签页中(以分号分隔),格式:*(tab_name=通配符) 当 bvar_dump_file 不为空时,会启动一个后台线程,按指定的时间间隔(称为
bvar_dump_interval)更新bvar_dump_file,其中包括所有匹配bvar_dump_include但不匹配bvar_dump_exclude的 bvar例如,我们将 gflags 修改如下:
导出的文件内容如下:
$ cat bvar.echo_server.data rpc_server_8002_builtin_service_count : 20 rpc_server_8002_connection_count : 1 rpc_server_8002_nshead_service_adaptor : brpc::policy::NovaServiceAdaptor rpc_server_8002_service_count : 1 rpc_server_8002_start_time : 2015/07/24-21:08:03 rpc_server_8002_uptime_ms : 14740954iobuf_block_count : 8会被 bvar_dump_include 过滤掉,rpc_server_8002_error : 0则会被 bvar_dump_exclude 排除。如果你的程序中没有使用 brpc,你还需要动态修改 gflags(通常不需要),可以调用 google::SetCommandLineOption(),如下所示
#include <gflags/gflags.h> ... if (google::SetCommandLineOption("bvar_dump_include", "*service*").empty()) { LOG(ERROR) << "Fail to set bvar_dump_include"; return -1; } LOG(INFO) << "Successfully set bvar_dump_include to *service*";不要直接设置 FLAGS_bvar_dump_file / FLAGS_bvar_dump_include / FLAGS_bvar_dump_exclude。一方面,这些 gflags 的类型是 std::string,对其赋值不是线程安全的;另一方面,validator 不会被触发(回调检查其正确性),因此导出线程不会被调用。
用户也可以自定义 dump_exposed() 来导出所有已暴露的 bvar:
// Implement this class to write variables into different places. // If dump() returns false, Variable::dump_exposed() stops and returns -1. class Dumper { public: virtual bool dump(const std::string& name, const butil::StringPiece& description) = 0; }; // Options for Variable::dump_exposed(). struct DumpOptions { // Contructed with default options. DumpOptions(); // If this is true, string-type values will be quoted. bool quote_string; // The ? in wildcards. Wildcards in URL need to use another character // because ? is reserved. char question_mark; // Separator for white_wildcards and black_wildcards. char wildcard_separator; // Name matched by these wildcards (or exact names) are kept. std::string white_wildcards; // Name matched by these wildcards (or exact names) are skipped. std::string black_wildcards; }; class Variable { ... ... // Find all exposed variables matching `white_wildcards' but // `black_wildcards' and send them to `dumper'. // Use default options when `options' is NULL. // Return number of dumped variables, -1 on error. static int dump_exposed(Dumper* dumper, const DumpOptions* options); };
bvar::Reducer
Reducer 使用二元运算符将多个值合并为一个最终结果,该运算符必须满足交换律、结合律且无副作用。只有同时满足这三个条件,才能确保合并结果不受线程私有变量分布的影响。例如减法既不满足结合律也不满足交换律,因此不能作为这里的运算符。
// Reduce multiple values into one with `Op': e1 Op e2 Op e3 ...
// `Op' shall satisfy:
// - associative: a Op (b Op c) == (a Op b) Op c
// - commutative: a Op b == b Op a;
// - no side effects: a Op b never changes if a and b are fixed.
// otherwise the result is undefined.
template <typename T, typename Op>
class Reducer : public Variable;reducer « e1 « e2 « e3 等价于 reducer = e1 op e2 op e3。常见的 Reducer 子类:bvar::Adder、bvar::Maxer、bvar::Miner
bvar::Adder
顾名思义,它是用于求和的。运算符是 +
bvar::Adder<int> value;
value << 1 << 2 << 3 << -4;
CHECK_EQ(2, value.get_value());
bvar::Adder<double> fp_value; // may have warning
fp_value << 1.0 << 2.0 << 3.0 << -4.0;
CHECK_DOUBLE_EQ(2.0, fp_value.get_value());Adder<> 可以作用于非基本类型,该类型至少需要重写 T operator+(T, T),一个现有的例子是 std::string,下面的代码会将字符串拼接起来:
// This is just proof-of-concept, don't use it for production code because it makes a
// bunch of temporary strings which is not efficient, use std::ostringstream instead.
bvar::Adder<std::string> concater;
std::string str1 = "world";
concater << "hello " << str1;
CHECK_EQ("hello world", concater.get_value());bvar::Maxer
用于产生最大值,operator 为 std::max。
bvar::Maxer<int> value;
value << 1 << 2 << 3 << -4;
CHECK_EQ(3, value.get_value());由于 Maxer<> 使用 std::numeric_limits::min() 作为单位元,因此它无法应用于泛型类型,除非你对 std::numeric_limits<> 进行了特化(并且重载了 operator<,注意是 operator<,不是 operator>)。
bvar::Miner
产出最小值,对应的运算符是 std::min
bvar::Maxer<int> value;
value << 1 << 2 << 3 << -4;
CHECK_EQ(-4, value.get_value());由于 Miner<> 使用 std::numeric_limits::max() 作为单位元,因此除非你特化了 std::numeric_limits<>(并重载了 operator<),否则它无法应用于泛型类型。
bvar::IntRecorder
用于求平均值。
// For calculating average of numbers.
// Example:
// IntRecorder latency;
// latency << 1 << 3 << 5;
// CHECK_EQ(3, latency.average());
class IntRecorder : public Variable;bvar::LatencyRecoder
一个用于统计延迟与 QPS 的计数器。只要填入延迟数据,即可获取延迟 / 最大延迟 / QPS / 计数。时间窗口为最后一个参数,可省略,见 bvar_dump_interval
LatencyRecoder 是一个复合变量,由若干个 bvar 组成。
LatencyRecorder write_latency("table2_my_table_write"); // produces 4 variables:
// table2_my_table_write_latency
// table2_my_table_write_max_latency
// table2_my_table_write_qps
// table2_my_table_write_count
// In your write function
write_latency << the_latency_of_write;bvar::Window
获取时间窗口内的数据。Window 不能单独存在,它依赖于一个计数器。Window 会自动更新,我们不需要向它发送数据。出于性能考虑,数据来源于对原始计数器每秒一次的采样,在最坏情况下,Window 存在一秒的延迟。
// Get data within a time window.
// The time unit is 1 second fixed.
// Window relies on other bvar which should be constructed before this window and destructs after this window.
// R must:
// - have get_sampler() (not require thread-safe)
// - defined value_type and sampler_type
template <typename R>
class Window : public Variable;bvar::PerSecond
获取最近一段时间内的平均值。它与 Window 几乎相同,区别在于其值会被时间窗口所除。
bvar::Adder<int> sum;
// sum_per_second.get_value()is summing every-second value over the last 60 seconds, if we omit the time window, it's set to 'bvar_dump_interval' by default
bvar::PerSecond<bvar::Adder<int> > sum_per_second(&sum, 60);PerSecond 并非总是有意义
上面的代码中没有 Maxer,因为一段时间内的最大值除以时间窗口是没有意义的。
bvar::Maxer<int> max_value;
// WRONG!max value divided by time window is pointless
bvar::PerSecond<bvar::Maxer<int> > max_value_per_second_wrong(&max_value);
// CORRECT. It's the right way to set the time window to 1s so that we can get the max value for every second
bvar::Window<bvar::Maxer<int> > max_value_per_second(&max_value, 1);Window 与 PerSecond 的区别
假设我们想获取过去一分钟内的内存变化量,如果使用 Window<>,返回值的含义是"过去一分钟内内存增加了 18M";如果使用 PerSecond<>,返回值的含义则是"过去一分钟内平均每秒内存增加 0.3M"。
Window 的优点是精确,适用于数值较小的场景,比如"过去一分钟内产生的错误数"。如果使用 PerSecond,得到的结果可能是"过去一分钟内平均每秒错误率为 0.0167",这远不如"过去一分钟内产生了一个错误"来得直观。还有一些与时间无关的变量同样适合使用 Window<>,例如计算过去一分钟的 CPU 占比:用一个 Adder 对 CPU 时间和实际时间求和,然后在其上使用 Window<> 得到过去一分钟的 CPU 时间和实际时间,将两者相除即可得到过去一分钟的 CPU 占比,这个结果与时间无关。使用 PerSecond 则会得到错误的结果。
bvar::Status
记录并展示一个值,额外提供 set_value() 函数。
// Display a rarely or periodically updated value.
// Usage:
// bvar::Status<int> foo_count1(17);
// foo_count1.expose("my_value");
//
// bvar::Status<int> foo_count2;
// foo_count2.set_value(17);
//
// bvar::Status<int> foo_count3("my_value", 17);
//
// Notice that Tp needs to be std::string or acceptable by boost::atomic<Tp>.
template <typename Tp>
class Status : public Variable;bvar::PassiveStatus
按需显示数值。在某些情况下,我们既无法主动调用 set_value,也无法在某个时间间隔内调用 set_value。此时最好在需要时再输出该数值,用户可以传入一个输出回调函数来实现这一目标。
// Display a updated-by-need value. This is done by passing in an user callback
// which is called to produce the value.
// Example:
// int print_number(void* arg) {
// ...
// return 5;
// }
//
// // number1 : 5
// bvar::PassiveStatus status1("number1", print_number, arg);
//
// // foo_number2 : 5
// bvar::PassiveStatus status2(typeid(Foo), "number2", print_number, arg);
template <typename Tp>
class PassiveStatus : public Variable;
even though it looks simple, PassiveStatus is one of the most useful bvar, since most of the statistic values have already existed, we don't have to store it again, just fetch the data according to our need. Declare a bvar which can display user process name as below:
static void get_username(std::ostream& os, void*) {
char buf[32];
if (getlogin_r(buf, sizeof(buf)) == 0) {
buf[sizeof(buf)-1] = '\0';
os << buf;
} else {
os << "unknown";
}
}
PassiveStatus<std::string> g_username("process_username", get_username, NULL);bvar::GFlag
将重要的 gflags 暴露为 bvar,使其能够(在 noah 中)被监控。
DEFINE_int32(my_flag_that_matters, 8, "...");
// Expose the gflag as *same-named* bvar so that it's monitored (in noah).
static bvar::GFlag s_gflag_my_flag_that_matters("my_flag_that_matters");
// ^
// the gflag name
// Expose the gflag as a bvar named "foo_bar_my_flag_that_matters".
static bvar::GFlag s_gflag_my_flag_that_matters_with_prefix("foo_bar", "my_flag_that_matters");最后修改于 2023 年 1 月 10 日:[移除 incubator (devlive-community/knowforge#122)] (7647361c1)](https://github.com/apache/brpc-website/commit/7647361c1abc7392bf245411dab7863ec0a2d667)
评论
登录后参与评论
KnowForge