【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 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 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 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);

导出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::Window

bvar::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::WindowEx

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

Difference between bvar::PerSecondEx and bvar::PerSecond

    bvar::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");

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

4、浏览器插件配置使用

文章目录 一、Hackbar1. Load和Execute功能的使用2. Split功能的使用3. Post功能的使用4. 编码功能的使用 二、FoxyProxy1、设置Burpsuite的代理服务端口2、FoxyProxy插件的简单使用 三、User-Agent Switcher 一、Hackbar 火狐浏览器中按下F12键启用hackbar。 1. Load和Execut…

springboot宠物店管理系统-计算机毕设 附源码 32041

SpringBoot宠物店管理系统 摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;宠物行业当然也不例外。宠物店管理系统是以实际运用为开发背景&#xff0c;运用软件工程原理…

leetcode算法之链表

目录 1.两数相加2.两两交换链表中的节点3.重排链表4.合并K个升序链表5.K个一组翻转链表 1.两数相加 两数相加 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(…

Docker Swarm总结+service创建和部署、overlay网络以及Raft算法(2/4)

博主介绍&#xff1a;Java领域优质创作者,博客之星城市赛道TOP20、专注于前端流行技术框架、Java后端技术领域、项目实战运维以及GIS地理信息领域。 &#x1f345;文末获取源码下载地址&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb;…

鸿蒙开发-ArkTS 语言-基础语法

1. 初识 ArkTS 语言 ArkTS 是 HarmonyOS 优选主力开发语言。ArkTS 是基于 TypeScript (TS) 扩展的一门语言&#xff0c;继承了 TS 的所有特性&#xff0c;是TS的超集。 主要是扩展了以下几个方面&#xff1a; 声明式UI描述和自定义组件&#xff1a; ArkTS使用声明式的方式描述用…

存算一体还是存算分离?谈谈数据库基础设施的架构选择

从一则用户案例说起 某金融用户问&#xff0c;数据库用服务器本地盘性能好还是外置存储好&#xff1f;直觉上&#xff0c;本地盘路径短性能应该更好。然而测试结果却出乎意料&#xff1a;同等中等并发压力&#xff0c;混合随机读写模型&#xff0c;服务器本地SSD盘合计4万 IOPS…

提示工程-Prompt Engineering

提示工程 提示工程 1、概述 Prompt Engineering&#xff1a; 提示工程 通过自然语言&#xff08;英语、汉语等&#xff09;来给AI下达指示&#xff0c;从而让AI完成你指定给他的工作的过程都可以称之为提示工程。&#xff08;面向自然语言编程&#xff09; 提示词要素 指令&…

C语言公交车之谜(ZZULIOJ1232:公交车之谜)

题目描述 听说郑州紫荆山公园有英语口语角&#xff0c;还有很多外国人呢。为了和老外对上几句&#xff0c;这周六早晨birdfly拉上同伴早早的就坐上了72路公交从学校向紫荆山进发。一路上没事干&#xff0c;birdfly开始思考一个问题。 从学校到紫荆山公园共有n(1<n<20)站路…

Day02嵌入式---按键控灯

一、简单介绍 按键控制灯开关是一种常见的嵌入式系统示例项目&#xff0c;它通常用于演示嵌入式系统的基本控制能力。该项目由一个或多个LED和一个按键组成。通过按下按键&#xff0c;可以控制LED的开关状态&#xff0c;从而实现灯的亮灭控制。 二、查看功能手册 2.1 查看硬件…

学习笔记:如何分析财务报表

其实财务报表分析最核心的东西&#xff0c;是通过财务报表这个结果&#xff0c;由果推因&#xff0c;找出造成这个结果的原因。 会计是商业的语言 首先第一个问题是——会计是商业的语言&#xff0c;这是会计的根本。 什么叫“语言”&#xff0c;就是可以通过它进行交流。比如…

队列实现栈VS栈实现队列

目录 【1】用队列实现栈 思路分析 ​ 易错总结 Queue.c&Queue.h手撕队列 声明栈MyStack 创建&初始化栈myStackCreate 压栈myStackPush 出栈&返回栈顶元素myStackPop 返回栈顶元素myStackTop 判断栈空否myStackEmpty 释放空间myStackFree MyStack总代码…

2024年天津天狮学院专升本食品质量与安全专业《分析化学》考纲

2024年天津天狮学院食品质量与安全专业高职升本入学考试《分析化学》考试大纲 一、考试性质 《分析化学》专业课程考试是天津天狮学院食品质量与安全专业高职升本入学考试 的必考科目之一&#xff0c;其性质是考核学生是否达到了升入本科继续学习的要求而进行的选拔性考试。《…

第十七章 解读PyTorch断点训练(工具)

主要有以下几方面的内容&#xff1a; 对于多步长训练需要保存lr_schedule初始化随机数种子保存每一代最好的结果 简单详细介绍 最近在尝试用CIFAR10训练分类问题的时候&#xff0c;由于数据集体量比较大&#xff0c;训练的过程中时间比较长&#xff0c;有时候想给停下来&…

python-opencv 人脸68点特征点检测

python-opencv 人脸68点特征点检测 不是很难&#xff0c;主要还是掉包&#xff0c;来看一下代码啊&#xff1a; # coding: utf-8 # 导包 import numpy as np import dlib import cv2class face_emotion(object):def __init__(self):# 人脸检测器对象&#xff0c;通过它拿到人…

Git 仓库越来越大?不要慌!试试这个简单方法,轻松秒瘦身

开局两张图 瘦身前瘦身后 目录 开局两张图前言下载 BFG克隆代码Git 仓库瘦身清理存储库储存库 GC推送仓库 Git 瘦身验证结语开源项目 前言 在进行项目开发的过程中&#xff0c;代码仓库的体积可能会逐渐增大&#xff0c;特别是在版本控制系统中保留了大量的历史提交记录和不必…

Kafka-TopicPartition

Kafka主题与分区 主题与分区 topic & partition&#xff0c;是Kafka两个核心的概念&#xff0c;也是Kafka的基本组织单元。 主题作为消息的归类&#xff0c;可以再细分为一个或多个分区&#xff0c;分区也可以看作对消息的二次归类。 分区的划分为kafka提供了可伸缩性、水…

Java中的泛型是什么?如何使用泛型类和泛型方法?

Java 中的泛型是一种编程机制&#xff0c;允许你编写可以与多种数据类型一起工作的代码&#xff0c;同时提供编译时类型检查以确保类型的安全性。泛型的主要目的是提高代码的可重用性、类型安全性和程序的整体性能。 泛型类&#xff08;Generic Class&#xff09;: 在泛型类中…

服务运营 |精选:病人向何处去?医院调度的几种建模方法(上)

推文作者&#xff1a; Shutian Li 编者按&#xff1a; 住院流程&#xff08;Inpatient Flow&#xff09;是一种通过协调和优化医院内部流程&#xff0c;以提高患者入院至出院期间的效率和质量的方法。住院流程通常通过医院内部信息系统和协同工作流程进行管理&#xff0c;以确…

Everything进行内网穿透搜索

文章目录 1\. 部署内网穿透1.1. 注册账号1.2. 登录1.3. 创建隧道 2\. 从外网访问Everything 借助cpolar可以让我们在公网上访问到本地的电脑 1. 部署内网穿透 1.1. 注册账号 在使用之前需要先进行注册cpolar cpolar secure introspectable tunnels to localhost 1.2. 登录 C…

②⑩① 【MySQL】什么是分库分表?拆分策略有什么?什么是MyCat?

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 目录 ②⑩① 【MySQL】什么是分库分表&#xf…