【brpc学习实践八】bvar及其应用

什么是bvar

bvar是多线程环境下的计数器类库,支持单维度bvar和多维度mbvar,方便记录和查看用户程序中的各类数值,它利用了thread local存储减少了cache bouncing,相比UbMonitor(百度内的老计数器库)几乎不会给程序增加性能开销,也快于竞争频繁的原子操作。brpc集成了bvar,/vars可查看所有曝光的bvar,/vars/VARNAME可查阅某个bvar,在brpc中的使用方法请查看vars。brpc大量使用了bvar提供统计数值,当你需要在多线程环境中计数并展现时,应该第一时间想到bvar。

什么场景使用bvar

我们上面虽然说了多线程计数场景可以用,但实际上还需要细分,bvar的实现原则核心是尽量避免多线程竞争资源,将能在一个线程处理的资源放到一个线程,如果不能一直一个线程处理,将读写进行解耦,写各自写,读的时候就去读所有竞争资源进行合并,它的本质是把写时的竞争转移到了读:读得合并所有写过的线程中的数据,而不可避免地变慢了。当你读写都很频繁或得基于最新值做一些逻辑判断时,你不应该用bvar。

怎么用bvar

bvar分单维度和多维度两种,也就是统计单个指标和多个指标。

单维度bvar

bvar类型

bvar有多个具体类型,可以自行去看源码,比较常用的有:
在这里插入图片描述
使用示例
我们只需要定义好bvar,在修改的线程里去写值即可。比如我们可以在任意代码里定义bvar::LatencyRecorder来统计代码延时

#include <bvar/bvar.h>...
bvar::LatencyRecorder g_latency_recorder("client");  // expose this recorder
... 
void foo() {...g_latency_recorder << my_latency;...
}

定义bvar的示例如下:

#include <bvar/bvar.h>namespace foo {
namespace bar {// bvar::Adder<T>用于累加,下面定义了一个统计read error总数的Adder。
bvar::Adder<int> g_read_error;
// 设定bvar::Adder<int>这个bvar类型在一段时间内的值,也就是实现了窗口时间的累加。把bvar::Window套在其他bvar上就可以获得时间窗口内的值。可以不限于累加。
bvar::Window<bvar::Adder<int> > g_read_error_minute("foo_bar", "read_error", &g_read_error, 60);
//                                                     ^          ^                         ^
//                                                    前缀       监控项名称                  60秒,忽略则为10秒// bvar::LatencyRecorder是一个复合变量,可以统计:总量、qps、平均延时,延时分位值,最大延时。
bvar::LatencyRecorder g_write_latency("foo_bar", "write");
//                                      ^          ^
//                                     前缀       监控项,别加latency!LatencyRecorder包含多个bvar,它们会加上各自的后缀,比如write_qps, write_latency等等。// 定义一个统计“已推入task”个数的变量。
bvar::Adder<int> g_task_pushed("foo_bar", "task_pushed");
// 把bvar::PerSecond套在其他bvar上可以获得时间窗口内*平均每秒*的值,这里是每秒内推入task的个数。
bvar::PerSecond<bvar::Adder<int> > g_task_pushed_second("foo_bar", "task_pushed_second", &g_task_pushed);
//       ^                                                                                             ^
//    和Window不同,PerSecond会除以时间窗口的大小.                                   时间窗口是最后一个参数,这里没填,就是默认10秒。}  // bar
}  // foo

在应用的时候写入:

// 碰到read error
foo::bar::g_read_error << 1;// write_latency是23ms
foo::bar::g_write_latency << 23;// 推入了1个task
foo::bar::g_task_pushed << 1;

注意Window<>和PerSecond<>都是衍生变量不用给它赋值,会自动更新,因为我们已经将它套在一个bar上面了,bvar不管是作为局部变量还是全局变量,变量名是全局唯一的! 否则会曝光失败,如果-bvar_abort_on_same_name为true,程序会直接abort。

bvar命名规范

程序中有来自各种模块不同的bvar,为避免重名,建议如此命名:模块_类名_指标。

  • **模块:**一般是程序名,可以加上产品线的缩写,比如inf_ds,ecom_retrbs等等。
  • **类名:**一般是类名或函数名,比如storage_manager,
    file_transfer, rank_stage1等等。
  • **指标:**一般是count,qps,latency这类。

一些正确的命名如下:

iobuf_block_count : 29                          # 模块=iobuf   类名=block  指标=count
iobuf_block_memory : 237568                     # 模块=iobuf   类名=block  指标=memory
process_memory_resident : 34709504              # 模块=process 类名=memory 指标=resident
process_memory_shared : 6844416                 # 模块=process 类名=memory 指标=shared
rpc_channel_connection_count : 0                # 模块=rpc     类名=channel_connection  指标=count
rpc_controller_count : 1                        # 模块=rpc     类名=controller 指标=count
rpc_socket_count : 6                            # 模块=rpc     类名=socket     指标=count

目前bvar会做名字归一化,不管你打入的是foo::BarNum, foo.bar.num, foo bar num , foo-bar-num,最后都是foo_bar_num。

关于指标:

  • 个数以_count为后缀,比如request_count, error_count。
    每秒的个数以_second为后缀,比如request_second, process_inblocks_second,已经足够明确,不用写成_count_second或_per_second。

  • 每分钟的个数以_minute为后缀,比如request_minute, process_inblocks_minute

如果需要使用定义在另一个文件中的计数器,需要在头文件中声明对应的变量。

namespace foo {
namespace bar {
// 注意g_read_error_minute和g_task_pushed_second都是衍生的bvar,会自动更新,不要声明。
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 foo_count,在foo_qps.cpp中定义PerSecond<Adder > foo_qps(&foo_count);是错误的做法。

  • bvar是线程兼容的。你可以在不同的线程里操作不同的bvar。比如你可以在多个线程中同时expose或hide不同的bvar,它们会合理地操作需要共享的全局数据,是安全的。

  • 除了读写接口,bvar的其他函数都是线程不安全的:比如说你不能在多个线程中同时expose或hide同一个bvar,这很可能会导致程序crash。一般来说,读写之外的其他接口也没有必要在多个线程中同时操作。

计时可以使用butil::Timer,接口如下:

#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 timervoid start();// Stop this timervoid stop();// Get the elapse from start() to stop().int64_t n_elapsed() const;  // in nanosecondsint64_t u_elapsed() const;  // in microsecondsint64_t m_elapsed() const;  // in millisecondsint64_t s_elapsed() const;  // in seconds
};
}  // namespace butil

bvar variable – bvar的读取之道

Variable是所有bvar的基类,主要提供全局注册,列举,查询等功能。

用户以默认参数建立一个bvar时,这个bvar并未注册到任何全局结构中,在这种情况下,bvar纯粹是一个更快的计数器。我们称把一个bvar注册到全局表中的行为为“曝光”,可通过expose函数曝光:

// Expose this variable globally so that it's counted in following functions:
//   list_exposed
//   count_exposed
//   describe_exposed
//   find_exposed
// Return 0 on success, -1 otherwise.
int expose(const butil::StringPiece& name);
int expose_as(const butil::StringPiece& prefix, const butil::StringPiece& name);

全局曝光后的bvar名字便为name或prefix + name,可通过以_exposed为后缀的static函数查询,我们就可以在程序中进行读取、判断等操作。比如Variable::describe_exposed(name)会返回名为name的bvar的描述。

当相同名字的bvar已存在时,expose会打印FATAL日志并返回-1。如果选项 -bvar_abort_on_same_name设为true (默认是false),程序会直接abort。

下面是一些曝光bvar的例子:

bvar::Adder<int> count1;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 0bvar::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);

导出bvar

bvar导出方式

bvar提供两种常见的导出功能,

  1. 通过HTTP接口查询
  2. 写入本地文件

前者在brpc中通过**/vars服务**提供,我们后续会专门讲到,
后者则已实现在bvar中,默认不打开。有几种方法打开这个功能:

  • 用gflags解析输入参数,在程序启动时加入-bvar_dump
  • 在brpc中也可通过/flags服务在启动后动态修改

gflags的解析方法如下,在main函数处添加如下代码:

 #include <gflags/gflags.h>...int main(int argc, char* argv[]) {google::ParseCommandLineFlags(&argc, &argv, true/*表示把识别的参数从argc/argv中删除*/);...}

不想用gflags解析参数,希望直接在程序中默认打开,在main函数处添加如下代码:

#include <gflags/gflags.h>
...
int main(int argc, char* argv[]) {if (google::SetCommandLineOption("bvar_dump", "true").empty()) {LOG(FATAL) << "Fail to enable bvar dump";}...
}

bvar导出参数控制

dump功能由如下gflags控制:
在这里插入图片描述当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 : 14740954

像”iobuf_block_count : 8”被bvar_dump_include过滤了,“rpc_server_8002_error : 0”则被bvar_dump_exclude排除了。

如果你的程序没有使用brpc,仍需要动态修改gflag(一般不需要),可以调用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。 一方面这些gflag类型都是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应用方法

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。

常见的Redcuer子类有bvar::Adder, bvar::Maxer, bvar::Miner。

bvar::Adder

顾名思义,用于累加,Op为+。这里我们有新的get_value的方法来获取值。

bvar::Adder<int> value;
value << 1 << 2 << 3 << -4;
CHECK_EQ(2, value.get_value());bvar::Adder<double> fp_value;  // 可能有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,下面的代码会把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

用于取最大值,运算符为std::max。

bvar::Maxer<int> value;
value << 1 << 2 << 3 << -4;
CHECK_EQ(3, value.get_value());

Since Maxer<> use std::numeric_limits::min() as the identity, it cannot be applied to generic types unless you specialized std::numeric_limits<> (and overloaded operator<, yes, not operator>).

bvar::Miner

用于取最小值,运算符为std::min。

bvar::Maxer<int> value;
value << 1 << 2 << 3 << -4;
CHECK_EQ(-4, value.get_value());

Since Miner<> use std::numeric_limits::max() as the identity, it cannot be applied to generic types unless you specialized std::numeric_limits<> (and overloaded 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::LatencyRecorder

专用于计算latency和qps的计数器。只需填入latency数据,就能获得latency / max_latency / qps / count。统计窗口是最后一个参数,不填为bvar_dump_interval(这里没填)。

注意:LatencyRecorder没有继承Variable,而是多个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的数据来自于每秒一次对原计数器的采样,在最差情况下,Window的返回值有1秒的延时。

// 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;How to use bvar::Windowbvar::Adder<int> sum;
bvar::Maxer<int> max_value;
bvar::IntRecorder avg_value;// sum_minute.get_value()是sum在之前60秒内的累加值。
bvar::Window<bvar::Adder<int> > sum_minute(&sum, 60);// max_value_minute.get_value()是max_value在之前60秒内的最大值。
bvar::Window<bvar::Maxer<int> > max_value_minute(&max_value, 60);// avg_value_minute.get_value()是avg_value在之前60秒内的平均值。
bvar::Window<IntRecorder> avg_value_minute(&avg_value, 60);

bvar::PerSecond

获得之前一段时间内平均每秒的统计值。它和Window基本相同,除了返回值会除以时间窗口之外。

bvar::Adder<int> sum;// sum_per_second.get_value()是sum在之前60秒内*平均每秒*的累加值,省略最后一个时间窗口的话默认为bvar_dump_interval。
bvar::PerSecond<bvar::Adder<int> > sum_per_second(&sum, 60);

PerSecond并不总是有意义

上面的代码中没有Maxer,因为一段时间内的最大值除以时间窗口是没有意义的。

bvar::Maxer max_value;

// 错误!最大值除以时间是没有意义的
bvar::PerSecond<bvar::Maxer > max_value_per_second_wrong(&max_value);

// 正确,把Window的时间窗口设为1秒才是正确的做法
bvar::Window<bvar::Maxer > max_value_per_second(&max_value, 1);

Difference with Window

比如要统计内存在上一分钟内的变化,用Window<>的话,返回值的含义是”上一分钟内存增加了18M”,用PerSecond<>的话,返回值的含义是“上一分钟平均每秒增加了0.3M”。

Window的优点是精确值,适合一些比较小的量,比如“上一分钟的错误数“,如果这用PerSecond的话,得到可能是”上一分钟平均每秒产生了0.0167个错误",这相比于”上一分钟有1个错误“显然不够清晰。另外一些和时间无关的量也要用Window,比如统计上一分钟cpu占用率的方法是用一个Adder同时累加cpu时间和真实时间,然后用Window获得上一分钟的cpu时间和真实时间,两者相除就得到了上一分钟的cpu占用率,这和时间无关,用PerSecond会产生错误的结果。

bvar::WindowEx

获得之前一段时间内的统计值。WindowEx是独立存在的,不依赖其他的计数器,需要给它发送数据。出于性能考虑,WindowEx每秒对数据做一次统计,在最差情况下,WindowEx的返回值有1秒的延时。

// Get data within a time window.
// The time unit is 1 second fixed.
// Window not relies on other bvar.// R must:
// - window_size must be a constant
template <typename R, time_t window_size = 0>
class WindowEx : public adapter::WindowExAdapter<R, adapter::WindowExType<R> > {
public:typedef adapter::WindowExAdapter<R, adapter::WindowExType<R> > Base;WindowEx() : Base(window_size) {}WindowEx(const base::StringPiece& name) : Base(window_size) {this->expose(name);}WindowEx(const base::StringPiece& prefix,const base::StringPiece& name): Base(window_size) {this->expose_as(prefix, name);}
};

How to use bvar::WindowEx

const int window_size = 60;// sum_minute.get_value()是60秒内的累加值,省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。
bvar::WindowEx<bvar::Adder<int>, window_size> sum_minute("sum_minute");
sum_minute << 1 << 2 << 3;// max_minute.get_value()是60秒内的最大值,省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。
bvar::WindowEx<bvar::Maxer<int>, window_size> max_minute("max_minute");
max_minute << 1 << 2 << 3;// min_minute.get_value()是60秒内的最小值,省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。
bvar::WindowEx<bvar::Miner<int>, window_size> min_minute("min_minute");
min_minute << 1 << 2 << 3;// avg_minute.get_value是60秒内的平均值(返回值是bvar::Stat),省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。
bvar::WindowEx<bvar::IntRecorder, window_size> avg_minute("avg_minute");
avg_minute << 1 << 2 << 3;
// 获得avg_minuter 60秒内的平均值stat
bvar::Stat avg_stat = avg_minute.get_value();
// 获得整型平均值
int64_t avg_int = avg_stat.get_average_int();
// 获得double类型平均值
double avg_double = avg_stat.get_average_double();Difference between bvar::WindowEx and bvar::Window

bvar::Window 不能独立存在,必须依赖于一个已有的计数器。Window会自动更新,不用给它发送数据;window_size是通过构造函数参数传递的。

bvar::WindowEx 是独立存在的,不依赖其他的计数器,需要给它发送数据。使用起来比较方便;window_size是通过模板参数传递的,省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。

bvar::PerSecondEx

获得之前一段时间内平均每秒的统计值。它和WindowEx基本相同,除了返回值会除以时间窗口之外。

// Get data per second within a time window.
// The only difference between PerSecondEx and WindowEx is that PerSecondEx divides
// the data by time duration.// R must:
// - window_size must be a constant
template <typename R, time_t window_size = 0>
class PerSecondEx : public adapter::WindowExAdapter<R, adapter::PerSecondExType<R> > {
public:typedef adapter::WindowExAdapter<R, adapter::PerSecondExType<R> > Base;PerSecondEx() : Base(window_size) {}PerSecondEx(const base::StringPiece& name) : Base(window_size) {this->expose(name);}PerSecondEx(const base::StringPiece& prefix,const base::StringPiece& name): Base(window_size) {this->expose_as(prefix, name);}
};

How to use bvar::PerSecondEx

const int window_size = 60;// sum_per_second.get_value()是60秒内*平均每秒*的累加值,省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。
bvar::PerSecondEx<bvar::Adder<int>, window_size> sum_per_second("sum_per_second");
sum_per_second << 1 << 2 << 3;Difference between bvar::PerSecondEx and bvar::WindowExbvar::PerSecondEx 获得之前一段时间内平均每秒的统计值。它和WindowEx基本相同,除了返回值会除以时间窗口之外。Difference between bvar::PerSecondEx and bvar::PerSecondbvar::PerSecond 不能独立存在,必须依赖于一个已有的计数器。PerSecond会自动更新,不用给它发送数据;window_size是通过构造函数参数传递的。bvar::PerSecondEx 是独立存在的,不依赖其他的计数器,需要给它发送数据。使用起来比较方便;window_size是通过模板参数传递的,省略最后一个window_size(时间窗口)的话默认为bvar_dump_interval。

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;

虽然很简单,但PassiveStatus是最有用的bvar之一,因为很多统计量已经存在,我们不需要再次存储它们,而只要按需获取。比如下面的代码声明了一个在linux下显示进程用户名的bvar:

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

Expose important gflags as bvar so that they're monitored.DEFINE_int32(my_flag_that_matters, 8, "...");// Expose the gflag as *same-named* bvar so that it's monitored.
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");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/170967.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32 SCF文件

文章目录 1 SCF文件2 SCT分散加载文件3 SCF文件编写 1 SCF文件 keil编译器在链接的时候&#xff0c;是根据分散加载(.scf后缀的文件)来确定程序的加载域和运行域的。 加载域就是程序运行前在flash中具体分区情况执行域就是程序运行后&#xff0c;程序在flash和ram中的分区情况…

在Windows系统上安装git-Git的过程记录

01-上git的官网下载git的windows安装版本 下载页面链接&#xff1a; https://git-scm.com/downloads 选择Standalone Installer的版本进行下载&#xff1a; 这里给大家一全git-2.43.0的百度网盘下载链接&#xff1a; https://pan.baidu.com/s/11HwNTCZmtSWj0VG2x60HIA?pwdut…

Linux 基础-常用的命令和搭建 Java 部署环境

文章目录 目录相关查看目录中的内容查看目录当前的完整路径切换目录 文件相关创建文件查看文件内容写文件vim 基础 创建删除创建目录 移动和复制移动(剪切粘贴)复制(复制粘贴) 搭建 Java 部署环境1. 安装 jdk2. 安装 tomcat1). 我们在自己电脑上下好 tomcat2). 从官网下载的 .z…

MySQL的基础知识

目录 关系型数据库 SQL通用语法 数据类型 数值类型 字符串类型 日期类型 SQL分类 DDL 数据库操作 表操作 DML 添加数据 修改数据 删除数据 DQL 基本查询 条件查询 聚合函数 分组查询 排序查询 分页查询 执行顺序 DCL 管理用户 权限控制 函数 字符串…

iar如何全擦芯片内存

Project ->Download -> Erase memory

js逆向-某敏感网站登录参数分析

声明 本文仅供学习参考&#xff0c;如有侵权可私信本人删除&#xff0c;请勿用于其他途径&#xff0c;违者后果自负&#xff01; 如果觉得文章对你有所帮助&#xff0c;可以给博主点击关注和收藏哦&#xff01; 前言 目标网站&#xff1a;aHR0cHM6Ly9tZGZnaGcuNXhwb2lqaHRm…

五种多目标优化算法(MSSA、MOAHA、MOPSO、NSGA3、NSGA2)求解微电网多目标优化调度(MATLAB)

一、多目标优化算法简介 &#xff08;1&#xff09;多目标鳟海鞘算法MSSA 多目标优化算法&#xff1a;多目标鳟海鞘算法&#xff08;Multi-objective Salp Swarm Algorithm &#xff0c;MSSA&#xff09;-CSDN博客 参考文献&#xff1a; S. Mirjalili, A.H. Gandomi, S.Z. M…

flex布局实战之自动填充剩余

案例目标 文字部分自适应并且居中 图中是一个弹窗&#xff0c;我现在使用flex的布局来实现&#xff0c;标题和关闭按钮。因为是uni-app,所以标签是view 。你可以自行替换为 代码 <view class"popup-box"><view class"title"><view class&…

线程的状态以及状态转移

一. 线程的状态 NEW: 线程刚被创建, 但是并未启动. 还没调用start方法.RUNNABLE: 这里没有区分就绪和运行状态. 因为对于Java对象来说, 只能标记为可运行, 至于什么时候运行, 不是JVM来控制的了, 是OS来进行调度的, 而且时间非常短暂, 因此对于Java对象的状态来说, 无法区分.T…

97、Text2NeRF: Text-Driven 3D Scene Generation with Neural Radiance Fields

简介 论文地址 使用扩散模型来推断文本相关图像作为内容先验&#xff0c;并使用单目深度估计方法来提供几何先验&#xff0c;并引入了一种渐进的场景绘制和更新策略&#xff0c;保证不同视图之间纹理和几何的一致性 实现流程 简单而言&#xff1a; 文本-图片扩散模型生成一…

STM32入门学习(一):STM32 简介与软件安装

参考引用 STM32 入门教程-江科协 1. STM32 简介 1.1 STM32 套件介绍 1.2 STM32 简介 STM32 是 ST 公司基于 ARM Cortex-M 内核开发的 32 位微控制器 应用&#xff1a;嵌入式领域&#xff0c;如智能车、无人机、机器人、无线通信、物联网、工业控制、娱乐电子产品等 1.3 ARM …

go对rabbitmq基本操作

一、安装rabbitmq 1、直接使用docker拉取镜像 docker pull rabbitmq:3.82、启动容器 docker run \-e RABBITMQ_DEFAULT_USERadmin \-e RABBITMQ_DEFAULT_PASS123456 \-v mq-plugins:/plugins \--name rabbit01 \--hostname rabbit01 --restartalways \-p 15672:15672 \-p 5672:…

《C++PrimePlus》第9章 内存模型和名称空间

9.1 单独编译 Visual Studio中新建头文件和源代码 通过解决方案资源管理器&#xff0c;如图所示&#xff1a; 分成三部分的程序&#xff08;直角坐标转换为极坐标&#xff09; 头文件coordin.h #ifndef __COORDIN_H__ // 如果没有被定义过 #define __COORDIN_H__struct pola…

【开源】基于Vue.js的城市桥梁道路管理系统的设计和实现

项目编号&#xff1a; S 025 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S025&#xff0c;文末获取源码。} 项目编号&#xff1a;S025&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询城市桥…

学生信息管理系统程序Python

系统主界面 在该界面中可以选择要使用功能对应的菜单进行不同的操作。在选择功能菜单时&#xff0c;有两种方法&#xff0c; 一种是输入1&#xff0c;另一种是按下键盘上的↑或↓方向键进行选择。这两种方法的结果是一样的&#xff0c;所以使用哪种方法都可以。 &#xff08;…

时间序列预测 — Informer实现多变量负荷预测(PyTorch)

目录 1 实验数据集 2 如何运行自己的数据集 3 报错分析 1 实验数据集 实验数据集采用数据集4&#xff1a;2016年电工数学建模竞赛负荷预测数据集&#xff08;下载链接&#xff09;&#xff0c;数据集包含日期、最高温度℃ 、最低温度℃、平均温度℃ 、相对湿度(平均) 、降雨…

什么是零拷贝 、零拷贝优化方案 - 真正的零拷贝,哪些地方会用到零拷贝技术

文章目录 什么是零拷贝3、零拷贝优化方案 - 真正的零拷贝哪些地方会用到零拷贝技术 现在来谈谈零拷贝&#xff0c;以及在开发中哪些地方使用到零拷贝。 开干… 什么是零拷贝 零拷贝指的是&#xff0c;从一个存储区域到另一个存储区域的copy任务无需CPU参与就可完成。零拷贝的底…

徕芬不是满分:自称超越戴森,用户称多次故障,品控仍是老大难?

撰稿|行星 来源|贝多财经 “双十一”购物节落下帷幕后&#xff0c;各大品牌纷纷公布“战报”。其中&#xff0c;高速吹风机品牌徕芬&#xff08;也称“徕芬科技”&#xff09;销售额超4.4亿元&#xff0c;全系产品销量超过80万台&#xff0c;高速吹风机系列单品(LF03、SE)销售…

来自Microsoft Teams的摄像头背景图片

原文件在&#x1f446;&#xff0c;下面是预览图 如果你安装了Microsoft Teams也可以搜索MSTeams&#xff0c;就在MSTeams/Backgrounds

【anaconda】numpy.dot 向量点乘小技巧

假设向量A[1,1], 向量B[2,3]。如果想知道他们的内积就可以输入如下代码: 当然&#xff0c;如果是两个列向量相乘&#xff0c;肯定是不对的 但是如果没有维度也一样可以求得内积&#xff0c;而且结果不会套在列表里