Coverage Report

Created: 2025-08-05 06:45

/src/brpc/src/bvar/variable.cpp
Line
Count
Source (jump to first uncovered line)
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
// Date: 2014/09/22 19:04:47
19
20
#include <pthread.h>
21
#include <set>                                  // std::set
22
#include <fstream>                              // std::ifstream
23
#include <sstream>                              // std::ostringstream
24
#include <gflags/gflags.h>
25
#include "butil/macros.h"                        // BAIDU_CASSERT
26
#include "butil/containers/flat_map.h"           // butil::FlatMap
27
#include "butil/scoped_lock.h"                   // BAIDU_SCOPE_LOCK
28
#include "butil/string_splitter.h"               // butil::StringSplitter
29
#include "butil/errno.h"                         // berror
30
#include "butil/time.h"                          // milliseconds_from_now
31
#include "butil/file_util.h"                     // butil::FilePath
32
#include "butil/threading/platform_thread.h"
33
#include "butil/reloadable_flags.h"
34
#include "bvar/gflag.h"
35
#include "bvar/variable.h"
36
#include "bvar/mvariable.h"
37
38
namespace bvar {
39
40
DEFINE_bool(save_series, true,
41
            "Save values of last 60 seconds, last 60 minutes,"
42
            " last 24 hours and last 30 days for plotting");
43
44
DEFINE_bool(quote_vector, true,
45
            "Quote description of Vector<> to make it valid to noah");
46
47
// Remember abort request before bvar_abort_on_same_name is initialized.
48
bool s_bvar_may_abort = false;
49
0
static bool validate_bvar_abort_on_same_name(const char*, bool v) {
50
0
    RELEASE_ASSERT_VERBOSE(!v || !s_bvar_may_abort, "Abort due to name conflict");
51
0
    return true;
52
0
}
53
DEFINE_bool(bvar_abort_on_same_name, false, "Abort when names of bvar are same");
54
BUTIL_VALIDATE_GFLAG(bvar_abort_on_same_name, validate_bvar_abort_on_same_name);
55
56
57
DEFINE_bool(bvar_log_dumpped,  false,
58
            "[For debugging] print dumpped info"
59
            " into logstream before call Dumpper");
60
BUTIL_VALIDATE_GFLAG(bvar_log_dumpped, butil::PassValidate);
61
62
const size_t SUB_MAP_COUNT = 32;  // must be power of 2
63
BAIDU_CASSERT(!(SUB_MAP_COUNT & (SUB_MAP_COUNT - 1)), must_be_power_of_2);
64
65
class VarEntry {
66
public:
67
8
    VarEntry() : var(NULL), display_filter(DISPLAY_ON_ALL) {}
68
69
    Variable* var;
70
    DisplayFilter display_filter;
71
};
72
73
typedef butil::FlatMap<std::string, VarEntry> VarMap;
74
75
struct VarMapWithLock : public VarMap {
76
    pthread_mutex_t mutex;
77
78
64
    VarMapWithLock() {
79
64
        if (init(1024) != 0) {
80
0
            LOG(WARNING) << "Fail to init VarMap";
81
0
        }
82
83
64
        pthread_mutexattr_t attr;
84
64
        pthread_mutexattr_init(&attr);
85
64
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
86
64
        pthread_mutex_init(&mutex, &attr);
87
64
        pthread_mutexattr_destroy(&attr);
88
64
    }
89
};
90
91
// We have to initialize global map on need because bvar is possibly used
92
// before main().
93
static pthread_once_t s_var_maps_once = PTHREAD_ONCE_INIT;
94
static VarMapWithLock* s_var_maps = NULL;
95
96
2
static void init_var_maps() {
97
    // It's probably slow to initialize all sub maps, but rpc often expose 
98
    // variables before user. So this should not be an issue to users.
99
2
    s_var_maps = new VarMapWithLock[SUB_MAP_COUNT];
100
2
}
101
102
8
inline size_t sub_map_index(const std::string& str) {
103
8
    if (str.empty()) {
104
0
        return 0;
105
0
    }
106
8
    size_t h = 0;
107
    // we're assume that str is ended with '\0', which may not be in general
108
170
    for (const char* p  = str.c_str(); *p; ++p) {
109
162
        h = h * 5 + *p;
110
162
    }
111
8
    return h & (SUB_MAP_COUNT - 1);
112
8
}
113
114
8
inline VarMapWithLock* get_var_maps() {
115
8
    pthread_once(&s_var_maps_once, init_var_maps);
116
8
    return s_var_maps;
117
8
}
118
119
8
inline VarMapWithLock& get_var_map(const std::string& name) {
120
8
    VarMapWithLock& m = get_var_maps()[sub_map_index(name)];
121
8
    return m;
122
8
}
123
124
0
Variable::~Variable() {
125
0
    CHECK(!hide()) << "Subclass of Variable MUST call hide() manually in their"
126
0
        " dtors to avoid displaying a variable that is just destructing";
127
0
}
128
129
int Variable::expose_impl(const butil::StringPiece& prefix,
130
                          const butil::StringPiece& name,
131
8
                          DisplayFilter display_filter) {
132
8
    if (name.empty()) {
133
0
        LOG(ERROR) << "Parameter[name] is empty";
134
0
        return -1;
135
0
    }
136
    // NOTE: It's impossible to atomically erase from a submap and insert into
137
    // another submap without a global lock. When the to-be-exposed name
138
    // already exists, there's a chance that we can't insert back previous
139
    // name. But it should be fine generally because users are unlikely to
140
    // expose a variable more than once and calls to expose() are unlikely
141
    // to contend heavily.
142
143
    // remove previous pointer from the map if needed.
144
8
    hide();
145
146
    // Build the name.
147
8
    _name.clear();
148
8
    _name.reserve((prefix.size() + name.size()) * 5 / 4);
149
8
    if (!prefix.empty()) {
150
0
        to_underscored_name(&_name, prefix);
151
0
        if (!_name.empty() && butil::back_char(_name) != '_') {
152
0
            _name.push_back('_');
153
0
        }
154
0
    }
155
8
    to_underscored_name(&_name, name);
156
    
157
8
    VarMapWithLock& m = get_var_map(_name);
158
8
    {
159
8
        BAIDU_SCOPED_LOCK(m.mutex);
160
8
        VarEntry* entry = m.seek(_name);
161
8
        if (entry == NULL) {
162
8
            entry = &m[_name];
163
8
            entry->var = this;
164
8
            entry->display_filter = display_filter;
165
8
            return 0;
166
8
        }
167
8
    }
168
0
    RELEASE_ASSERT_VERBOSE(!FLAGS_bvar_abort_on_same_name,
169
0
                           "Abort due to name conflict");
170
0
    if (!s_bvar_may_abort) {
171
        // Mark name conflict occurs, If this conflict happens before
172
        // initialization of bvar_abort_on_same_name, the validator will
173
        // abort the program if needed.
174
0
        s_bvar_may_abort = true;
175
0
    }
176
        
177
0
    LOG(ERROR) << "Already exposed `" << _name << "' whose value is `"
178
0
               << describe_exposed(_name) << '\'';
179
0
    _name.clear();
180
0
    return -1;
181
0
}
182
183
0
bool Variable::is_hidden() const {
184
0
    return _name.empty();
185
0
}
186
187
8
bool Variable::hide() {
188
8
    if (_name.empty()) {
189
8
        return false;
190
8
    }
191
0
    VarMapWithLock& m = get_var_map(_name);
192
0
    BAIDU_SCOPED_LOCK(m.mutex);
193
0
    VarEntry* entry = m.seek(_name);
194
0
    if (entry) {
195
0
        CHECK_EQ(1UL, m.erase(_name));
196
0
    } else {
197
0
        CHECK(false) << "`" << _name << "' must exist";
198
0
    }
199
0
    _name.clear();
200
0
    return true;
201
8
}
202
203
void Variable::list_exposed(std::vector<std::string>* names,
204
0
                            DisplayFilter display_filter) {
205
0
    if (names == NULL) {
206
0
        return;
207
0
    }
208
0
    names->clear();
209
0
    if (names->capacity() < 32) {
210
0
        names->reserve(count_exposed());
211
0
    }
212
0
    VarMapWithLock* var_maps = get_var_maps();
213
0
    for (size_t i = 0; i < SUB_MAP_COUNT; ++i) {
214
0
        VarMapWithLock& m = var_maps[i];
215
0
        std::unique_lock<pthread_mutex_t> mu(m.mutex);
216
0
        size_t n = 0;
217
0
        for (VarMap::const_iterator it = m.begin(); it != m.end(); ++it) {
218
0
            if (++n >= 256/*max iterated one pass*/) {
219
0
                VarMap::PositionHint hint;
220
0
                m.save_iterator(it, &hint);
221
0
                n = 0;
222
0
                mu.unlock();  // yield
223
0
                mu.lock();
224
0
                it = m.restore_iterator(hint);
225
0
                if (it == m.begin()) { // resized
226
0
                    names->clear();
227
0
                }
228
0
                if (it == m.end()) {
229
0
                    break;
230
0
                }
231
0
            }
232
0
            if (it->second.display_filter & display_filter) {
233
0
                names->push_back(it->first);
234
0
            }
235
0
        }
236
0
    }
237
0
}
238
239
0
size_t Variable::count_exposed() {
240
0
    size_t n = 0;
241
0
    VarMapWithLock* var_maps = get_var_maps();
242
0
    for (size_t i = 0; i < SUB_MAP_COUNT; ++i) {
243
0
        n += var_maps[i].size();
244
0
    }
245
0
    return n;
246
0
}
247
248
int Variable::describe_exposed(const std::string& name, std::ostream& os,
249
                               bool quote_string,
250
0
                               DisplayFilter display_filter) {
251
0
    VarMapWithLock& m = get_var_map(name);
252
0
    BAIDU_SCOPED_LOCK(m.mutex);
253
0
    VarEntry* p = m.seek(name);
254
0
    if (p == NULL) {
255
0
        return -1;
256
0
    }
257
0
    if (!(display_filter & p->display_filter)) {
258
0
        return -1;
259
0
    }
260
0
    p->var->describe(os, quote_string);
261
0
    return 0;
262
0
}
263
264
std::string Variable::describe_exposed(const std::string& name,
265
                                       bool quote_string,
266
0
                                       DisplayFilter display_filter) {
267
0
    std::ostringstream oss;
268
0
    if (describe_exposed(name, oss, quote_string, display_filter) == 0) {
269
0
        return oss.str();
270
0
    }
271
0
    return std::string();
272
0
}
273
274
0
std::string Variable::get_description() const {
275
0
    std::ostringstream os;
276
0
    describe(os, false);
277
0
    return os.str();
278
0
}
279
280
#ifdef BAIDU_INTERNAL
281
void Variable::get_value(boost::any* value) const {
282
    std::ostringstream os;
283
    describe(os, false);
284
    *value = os.str();
285
}
286
#endif
287
288
int Variable::describe_series_exposed(const std::string& name,
289
                                      std::ostream& os,
290
0
                                      const SeriesOptions& options) {
291
0
    VarMapWithLock& m = get_var_map(name);
292
0
    BAIDU_SCOPED_LOCK(m.mutex);
293
0
    VarEntry* p = m.seek(name);
294
0
    if (p == NULL) {
295
0
        return -1;
296
0
    }
297
0
    return p->var->describe_series(os, options);
298
0
}
299
300
#ifdef BAIDU_INTERNAL
301
int Variable::get_exposed(const std::string& name, boost::any* value) {
302
    VarMapWithLock& m = get_var_map(name);
303
    BAIDU_SCOPED_LOCK(m.mutex);
304
    VarEntry* p = m.seek(name);
305
    if (p == NULL) {
306
        return -1;
307
    }
308
    p->var->get_value(value);
309
    return 0;
310
}
311
#endif
312
313
// TODO(gejun): This is copied from otherwhere, common it if possible.
314
315
// Underlying buffer to store logs. Comparing to using std::ostringstream
316
// directly, this utility exposes more low-level methods so that we avoid
317
// creation of std::string which allocates memory internally.
318
class CharArrayStreamBuf : public std::streambuf {
319
public:
320
0
    explicit CharArrayStreamBuf() : _data(NULL), _size(0) {}
321
    ~CharArrayStreamBuf();
322
323
    int overflow(int ch) override;
324
    int sync() override;
325
    void reset();
326
0
    butil::StringPiece data() {
327
0
        return butil::StringPiece(pbase(), pptr() - pbase());
328
0
    }
329
330
private:
331
    char* _data;
332
    size_t _size;
333
};
334
335
0
CharArrayStreamBuf::~CharArrayStreamBuf() {
336
0
    free(_data);
337
0
}
338
339
0
int CharArrayStreamBuf::overflow(int ch) {
340
0
    if (ch == std::streambuf::traits_type::eof()) {
341
0
        return ch;
342
0
    }
343
0
    size_t new_size = std::max(_size * 3 / 2, (size_t)64);
344
0
    char* new_data = (char*)malloc(new_size);
345
0
    if (BAIDU_UNLIKELY(new_data == NULL)) {
346
0
        setp(NULL, NULL);
347
0
        return std::streambuf::traits_type::eof();
348
0
    }
349
0
    memcpy(new_data, _data, _size);
350
0
    free(_data);
351
0
    _data = new_data;
352
0
    const size_t old_size = _size;
353
0
    _size = new_size;
354
0
    setp(_data, _data + new_size);
355
0
    pbump(old_size);
356
    // if size == 1, this function will call overflow again.
357
0
    return sputc(ch);
358
0
}
359
360
0
int CharArrayStreamBuf::sync() {
361
    // data are already there.
362
0
    return 0;
363
0
}
364
365
0
void CharArrayStreamBuf::reset() {
366
0
    setp(_data, _data + _size);
367
0
}
368
369
370
// Written by Jack Handy
371
// <A href="mailto:jakkhandy@hotmail.com">jakkhandy@hotmail.com</A>
372
0
inline bool wildcmp(const char* wild, const char* str, char question_mark) {
373
0
    const char* cp = NULL;
374
0
    const char* mp = NULL;
375
376
0
    while (*str && *wild != '*') {
377
0
        if (*wild != *str && *wild != question_mark) {
378
0
            return false;
379
0
        }
380
0
        ++wild;
381
0
        ++str;
382
0
    }
383
384
0
    while (*str) {
385
0
        if (*wild == '*') {
386
0
            if (!*++wild) {
387
0
                return true;
388
0
            }
389
0
            mp = wild;
390
0
            cp = str+1;
391
0
        } else if (*wild == *str || *wild == question_mark) {
392
0
            ++wild;
393
0
            ++str;
394
0
        } else {
395
0
            wild = mp;
396
0
            str = cp++;
397
0
        }
398
0
    }
399
400
0
    while (*wild == '*') {
401
0
        ++wild;
402
0
    }
403
0
    return !*wild;
404
0
}
405
406
class WildcardMatcher {
407
public:
408
    WildcardMatcher(const std::string& wildcards,
409
                    char question_mark,
410
                    bool on_both_empty)
411
0
        : _question_mark(question_mark)
412
0
        , _on_both_empty(on_both_empty) {
413
0
        if (wildcards.empty()) {
414
0
            return;
415
0
        }
416
0
        std::string name;
417
0
        const char wc_pattern[3] = { '*', question_mark, '\0' };
418
0
        for (butil::StringMultiSplitter sp(wildcards.c_str(), ",;");
419
0
             sp != NULL; ++sp) {
420
0
            name.assign(sp.field(), sp.length());
421
0
            if (name.find_first_of(wc_pattern) != std::string::npos) {
422
0
                if (_wcs.empty()) {
423
0
                    _wcs.reserve(8);
424
0
                }
425
0
                _wcs.push_back(name);
426
0
            } else {
427
0
                _exact.insert(name);
428
0
            }
429
0
        }
430
0
    }
431
    
432
0
    bool match(const std::string& name) const {
433
0
        if (!_exact.empty()) {
434
0
            if (_exact.find(name) != _exact.end()) {
435
0
                return true;
436
0
            }
437
0
        } else if (_wcs.empty()) {
438
0
            return _on_both_empty;
439
0
        }
440
0
        for (size_t i = 0; i < _wcs.size(); ++i) {
441
0
            if (wildcmp(_wcs[i].c_str(), name.c_str(), _question_mark)) {
442
0
                return true;
443
0
            }
444
0
        }
445
0
        return false;
446
0
    }
447
448
0
    const std::vector<std::string>& wildcards() const { return _wcs; }
449
0
    const std::set<std::string>& exact_names() const { return _exact; }
450
451
private:
452
    char _question_mark;
453
    bool _on_both_empty;
454
    std::vector<std::string> _wcs;
455
    std::set<std::string> _exact;
456
};
457
458
DumpOptions::DumpOptions()
459
0
    : quote_string(true)
460
0
    , question_mark('?')
461
0
    , display_filter(DISPLAY_ON_PLAIN_TEXT)
462
0
{}
463
464
0
int Variable::dump_exposed(Dumper* dumper, const DumpOptions* poptions) {
465
0
    if (NULL == dumper) {
466
0
        LOG(ERROR) << "Parameter[dumper] is NULL";
467
0
        return -1;
468
0
    }
469
0
    DumpOptions opt;
470
0
    if (poptions) {
471
0
        opt = *poptions;
472
0
    }
473
0
    CharArrayStreamBuf streambuf;
474
0
    std::ostream os(&streambuf);
475
0
    int count = 0;
476
0
    WildcardMatcher black_matcher(opt.black_wildcards,
477
0
                                  opt.question_mark,
478
0
                                  false);
479
0
    WildcardMatcher white_matcher(opt.white_wildcards,
480
0
                                  opt.question_mark,
481
0
                                  true);
482
483
0
    std::ostringstream dumpped_info;
484
0
    const bool log_dummped = FLAGS_bvar_log_dumpped;
485
486
0
    if (white_matcher.wildcards().empty() &&
487
0
        !white_matcher.exact_names().empty()) {
488
0
        for (std::set<std::string>::const_iterator
489
0
                 it = white_matcher.exact_names().begin();
490
0
             it != white_matcher.exact_names().end(); ++it) {
491
0
            const std::string& name = *it;
492
0
            if (!black_matcher.match(name)) {
493
0
                if (bvar::Variable::describe_exposed(
494
0
                        name, os, opt.quote_string, opt.display_filter) != 0) {
495
0
                    continue;
496
0
                }
497
0
                if (log_dummped) {
498
0
                    dumpped_info << '\n' << name << ": " << streambuf.data();
499
0
                }
500
0
                if (!dumper->dump(name, streambuf.data())) {
501
0
                    return -1;
502
0
                }
503
0
                streambuf.reset();
504
0
                ++count;
505
0
            }
506
0
        }
507
0
    } else {
508
        // Have to iterate all variables.
509
0
        std::vector<std::string> varnames;
510
0
        bvar::Variable::list_exposed(&varnames, opt.display_filter);
511
        // Sort the names to make them more readable.
512
0
        std::sort(varnames.begin(), varnames.end());
513
0
        for (std::vector<std::string>::const_iterator
514
0
                 it = varnames.begin(); it != varnames.end(); ++it) {
515
0
            const std::string& name = *it;
516
0
            if (white_matcher.match(name) && !black_matcher.match(name)) {
517
0
                if (bvar::Variable::describe_exposed(
518
0
                        name, os, opt.quote_string, opt.display_filter) != 0) {
519
0
                    continue;
520
0
                }
521
0
                if (log_dummped) {
522
0
                    dumpped_info << '\n' << name << ": " << streambuf.data();
523
0
                }
524
0
                if (!dumper->dump(name, streambuf.data())) {
525
0
                    return -1;
526
0
                }
527
0
                streambuf.reset();
528
0
                ++count;
529
0
            }
530
0
        }
531
0
    }
532
0
    if (log_dummped) {
533
0
        LOG(INFO) << "Dumpped variables:" << dumpped_info.str();
534
0
    }
535
0
    return count;
536
0
}
537
538
539
// ============= export to files ==============
540
541
0
std::string read_command_name() {
542
0
    std::ifstream fin("/proc/self/stat");
543
0
    if (!fin.is_open()) {
544
0
        return std::string();
545
0
    }
546
0
    int pid = 0;
547
0
    std::string command_name;
548
0
    fin >> pid >> command_name;
549
0
    if (!fin.good()) {
550
0
        return std::string();
551
0
    }
552
    // Although the man page says the command name is in parenthesis, for
553
    // safety we normalize the name.
554
0
    std::string s;
555
0
    if (command_name.size() >= 2UL && command_name[0] == '(' &&
556
0
        butil::back_char(command_name) == ')') {
557
        // remove parenthesis.
558
0
        to_underscored_name(&s,
559
0
                            butil::StringPiece(command_name.data() + 1, 
560
0
                                              command_name.size() - 2UL));
561
0
    } else {
562
0
        to_underscored_name(&s, command_name);
563
0
    }
564
0
    return s;
565
0
}
566
567
class FileDumper : public Dumper {
568
public:
569
    FileDumper(const std::string& filename, butil::StringPiece s/*prefix*/)
570
0
        : _filename(filename), _fp(NULL) {
571
        // setting prefix.
572
        // remove trailing spaces.
573
0
        const char* p = s.data() + s.size();
574
0
        for (; p != s.data() && isspace(p[-1]); --p) {}
575
0
        s.remove_suffix(s.data() + s.size() - p);
576
        // normalize it.
577
0
        if (!s.empty()) {
578
0
            to_underscored_name(&_prefix, s);
579
0
            if (butil::back_char(_prefix) != '_') {
580
0
                _prefix.push_back('_');
581
0
            }
582
0
        }
583
0
    }
584
585
0
    ~FileDumper() {
586
0
        close();
587
0
    }
588
0
    void close() {
589
0
        if (_fp) {
590
0
            fclose(_fp);
591
0
            _fp = NULL;
592
0
        }
593
0
    }
594
595
protected:
596
0
    bool dump_impl(const std::string& name, const butil::StringPiece& desc, const std::string& separator) {
597
0
        if (_fp == NULL) {
598
0
            butil::File::Error error;
599
0
            butil::FilePath dir = butil::FilePath(_filename).DirName();
600
0
            if (!butil::CreateDirectoryAndGetError(dir, &error)) {
601
0
                LOG(ERROR) << "Fail to create directory=`" << dir.value()
602
0
                           << "', " << error;
603
0
                return false;
604
0
            }
605
0
            _fp = fopen(_filename.c_str(), "w");
606
0
            if (NULL == _fp) {
607
0
                LOG(ERROR) << "Fail to open " << _filename;
608
0
                return false;
609
0
            }
610
0
        }
611
0
        if (fprintf(_fp, "%.*s%.*s %.*s %.*s\r\n",
612
0
                    (int)_prefix.size(), _prefix.data(),
613
0
                    (int)name.size(), name.data(),
614
0
                    (int)separator.size(), separator.data(),
615
0
                    (int)desc.size(), desc.data()) < 0) {
616
0
            PLOG(ERROR) << "Fail to write into " << _filename;
617
0
            return false;
618
0
        }
619
0
        return true;
620
0
    }
621
private:
622
    std::string _filename;
623
    FILE* _fp;
624
    std::string _prefix;
625
};
626
627
class CommonFileDumper : public FileDumper {
628
public:
629
    CommonFileDumper(const std::string& filename, butil::StringPiece prefix)
630
0
        : FileDumper(filename, prefix)
631
0
        , _separator(":") {}
632
0
    bool dump(const std::string& name, const butil::StringPiece& desc) {
633
0
        return dump_impl(name, desc, _separator);
634
0
    }
635
private:
636
    std::string _separator;
637
};
638
639
class PrometheusFileDumper : public FileDumper {
640
public:
641
    PrometheusFileDumper(const std::string& filename, butil::StringPiece prefix)
642
0
        : FileDumper(filename, prefix)
643
0
        , _separator(" ") {}
644
0
    bool dump(const std::string& name, const butil::StringPiece& desc) {
645
0
        return dump_impl(name, desc, _separator);
646
0
    }
647
private:
648
    std::string _separator;
649
};
650
651
class FileDumperGroup : public Dumper {
652
public:
653
    FileDumperGroup(std::string tabs, std::string filename, 
654
0
                     butil::StringPiece s/*prefix*/) {
655
0
        butil::FilePath path(filename);
656
0
        if (path.FinalExtension() == ".data") {
657
            // .data will be appended later
658
0
            path = path.RemoveFinalExtension();
659
0
        }
660
661
0
        for (butil::KeyValuePairsSplitter sp(tabs, ';', '='); sp; ++sp) {
662
0
            std::string key = sp.key().as_string();
663
0
            std::string value = sp.value().as_string();
664
0
            FileDumper *f = new CommonFileDumper(
665
0
                    path.AddExtension(key).AddExtension("data").value(), s);
666
0
            WildcardMatcher *m = new WildcardMatcher(value, '?', true);
667
0
            dumpers.emplace_back(f, m);
668
0
        }
669
0
        dumpers.emplace_back(
670
0
                    new CommonFileDumper(path.AddExtension("data").value(), s), 
671
0
                    (WildcardMatcher *)NULL);
672
0
    }
673
0
    ~FileDumperGroup() {
674
0
        for (size_t i = 0; i < dumpers.size(); ++i) {
675
0
            delete dumpers[i].first;
676
0
            delete dumpers[i].second;
677
0
        }
678
0
        dumpers.clear();
679
0
    }
680
681
0
    bool dump(const std::string& name, const butil::StringPiece& desc) override {
682
0
        for (size_t i = 0; i < dumpers.size() - 1; ++i) {
683
0
            if (dumpers[i].second->match(name)) {
684
0
                return dumpers[i].first->dump(name, desc);
685
0
            }
686
0
        }
687
        // dump to default file
688
0
        return dumpers.back().first->dump(name, desc);
689
0
    }
690
private:
691
    std::vector<std::pair<FileDumper *, WildcardMatcher*> > dumpers;
692
};
693
694
static pthread_once_t dumping_thread_once = PTHREAD_ONCE_INIT;
695
static bool created_dumping_thread = false;
696
static pthread_mutex_t dump_mutex = PTHREAD_MUTEX_INITIALIZER;
697
static pthread_cond_t dump_cond = PTHREAD_COND_INITIALIZER;
698
699
DEFINE_bool(bvar_dump, false,
700
            "Create a background thread dumping all bvar periodically, "
701
            "all bvar_dump_* flags are not effective when this flag is off");
702
DEFINE_int32(bvar_dump_interval, 10, "Seconds between consecutive dump");
703
DEFINE_string(bvar_dump_file, "monitor/bvar.<app>.data", "Dump bvar into this file");
704
DEFINE_string(bvar_dump_include, "", "Dump bvar matching these wildcards, "
705
              "separated by semicolon(;), empty means including all");
706
DEFINE_string(bvar_dump_exclude, "", "Dump bvar excluded from these wildcards, "
707
              "separated by semicolon(;), empty means no exclusion");
708
DEFINE_string(bvar_dump_prefix, "<app>", "Every dumped name starts with this prefix");
709
DEFINE_string(bvar_dump_tabs, "latency=*_latency*"
710
                              ";qps=*_qps*"
711
                              ";error=*_error*"
712
                              ";system=*process_*,*malloc_*,*kernel_*",
713
              "Dump bvar into different tabs according to the filters (separated by semicolon), "
714
              "format: *(tab_name=wildcards;)");
715
716
DEFINE_bool(mbvar_dump, false,
717
            "Create a background thread dumping(shares the same thread as bvar_dump) all mbvar periodically, "
718
            "all mbvar_dump_* flags are not effective when this flag is off");
719
DEFINE_string(mbvar_dump_file, "monitor/mbvar.<app>.data", "Dump mbvar into this file");
720
DEFINE_string(mbvar_dump_prefix, "<app>", "Every dumped name starts with this prefix");
721
DEFINE_string(mbvar_dump_format, "common", "Dump mbvar write format");
722
723
#if !defined(BVAR_NOT_LINK_DEFAULT_VARIABLES)
724
// Expose bvar-releated gflags so that they're collected by noah.
725
// Maybe useful when debugging process of monitoring.
726
static GFlag s_gflag_bvar_dump_interval("bvar_dump_interval");
727
#endif
728
729
// The background thread to export all bvar periodically.
730
0
static void* dumping_thread(void*) {
731
    // NOTE: this variable was declared as static <= r34381, which was
732
    // destructed when program exits and caused coredumps.
733
0
    butil::PlatformThread::SetName("bvar_dumper");
734
0
    const std::string command_name = read_command_name();
735
0
    std::string last_filename;
736
0
    std::string mbvar_last_filename;
737
0
    while (1) {
738
        // We can't access string flags directly because it's thread-unsafe.
739
0
        std::string filename;
740
0
        DumpOptions options;
741
0
        std::string prefix;
742
0
        std::string tabs;
743
0
        std::string mbvar_filename;
744
0
        std::string mbvar_prefix;
745
0
        std::string mbvar_format;
746
0
        if (!GFLAGS_NAMESPACE::GetCommandLineOption("bvar_dump_file", &filename)) {
747
0
            LOG(ERROR) << "Fail to get gflag bvar_dump_file";
748
0
            return NULL;
749
0
        }
750
0
        if (!GFLAGS_NAMESPACE::GetCommandLineOption("bvar_dump_include",
751
0
                                          &options.white_wildcards)) {
752
0
            LOG(ERROR) << "Fail to get gflag bvar_dump_include";
753
0
            return NULL;
754
0
        }
755
0
        if (!GFLAGS_NAMESPACE::GetCommandLineOption("bvar_dump_exclude",
756
0
                                          &options.black_wildcards)) {
757
0
            LOG(ERROR) << "Fail to get gflag bvar_dump_exclude";
758
0
            return NULL;
759
0
        }
760
0
        if (!GFLAGS_NAMESPACE::GetCommandLineOption("bvar_dump_prefix", &prefix)) {
761
0
            LOG(ERROR) << "Fail to get gflag bvar_dump_prefix";
762
0
            return NULL;
763
0
        }
764
0
        if (!GFLAGS_NAMESPACE::GetCommandLineOption("bvar_dump_tabs", &tabs)) {
765
0
            LOG(ERROR) << "Fail to get gflags bvar_dump_tabs";
766
0
            return NULL;
767
0
        }
768
769
        // We can't access string flags directly because it's thread-unsafe.
770
0
        if (!GFLAGS_NAMESPACE::GetCommandLineOption("mbvar_dump_file", &mbvar_filename)) {
771
0
            LOG(ERROR) << "Fail to get gflag mbvar_dump_file";
772
0
            return NULL;
773
0
        }
774
0
        if (!GFLAGS_NAMESPACE::GetCommandLineOption("mbvar_dump_prefix", &mbvar_prefix)) {
775
0
            LOG(ERROR) << "Fail to get gflag mbvar_dump_prefix";
776
0
            return NULL;
777
0
        }
778
0
        if (!GFLAGS_NAMESPACE::GetCommandLineOption("mbvar_dump_format", &mbvar_format)) {
779
0
            LOG(ERROR) << "Fail to get gflag mbvar_dump_format";
780
0
            return NULL;
781
0
        }
782
783
0
        if (FLAGS_bvar_dump && !filename.empty()) {
784
            // Replace first <app> in filename with program name. We can't use
785
            // pid because a same binary should write the data to the same 
786
            // place, otherwise restarting of app may confuse noah with a lot 
787
            // of *.data. noah takes 1.5 days to figure out that some data is
788
            // outdated and to be removed.
789
0
            const size_t pos = filename.find("<app>");
790
0
            if (pos != std::string::npos) {
791
0
                filename.replace(pos, 5/*<app>*/, command_name);
792
0
            }
793
0
            if (last_filename != filename) {
794
0
                last_filename = filename;
795
0
                LOG(INFO) << "Write all bvar to " << filename << " every "
796
0
                          << FLAGS_bvar_dump_interval << " seconds.";
797
0
            }
798
0
            const size_t pos2 = prefix.find("<app>");
799
0
            if (pos2 != std::string::npos) {
800
0
                prefix.replace(pos2, 5/*<app>*/, command_name);
801
0
            }            
802
0
            FileDumperGroup dumper(tabs, filename, prefix);
803
0
            int nline = Variable::dump_exposed(&dumper, &options);
804
0
            if (nline < 0) {
805
0
                LOG(ERROR) << "Fail to dump vars into " << filename;
806
0
            }
807
0
        }
808
809
        // Dump multi dimension bvar
810
0
        if (FLAGS_mbvar_dump && !mbvar_filename.empty()) {
811
            // Replace first <app> in filename with program name. We can't use
812
            // pid because a same binary should write the data to the same 
813
            // place, otherwise restarting of app may confuse noah with a lot 
814
            // of *.data. noah takes 1.5 days to figure out that some data is
815
            // outdated and to be removed.
816
0
            const size_t pos = mbvar_filename.find("<app>");
817
0
            if (pos != std::string::npos) {
818
0
                mbvar_filename.replace(pos, 5/*<app>*/, command_name);
819
0
            }
820
0
            if (mbvar_last_filename != mbvar_filename) {
821
0
                mbvar_last_filename = mbvar_filename;
822
0
                LOG(INFO) << "Write all mbvar to " << mbvar_filename << " every "
823
0
                << FLAGS_bvar_dump_interval << " seconds.";
824
0
            }
825
0
            const size_t pos2 = mbvar_prefix.find("<app>");
826
0
            if (pos2 != std::string::npos) {
827
0
                mbvar_prefix.replace(pos2, 5/*<app>*/, command_name);
828
0
            }
829
830
0
            Dumper* dumper = NULL;
831
0
            if ("common" == mbvar_format) {
832
0
                dumper = new CommonFileDumper(mbvar_filename, mbvar_prefix);
833
0
            } else if ("prometheus" == mbvar_format) {
834
0
                dumper = new PrometheusFileDumper(mbvar_filename, mbvar_prefix);
835
0
            }
836
0
            int nline = MVariableBase::dump_exposed(dumper, &options);
837
0
            if (nline < 0) {
838
0
                LOG(ERROR) << "Fail to dump mvars into " << filename;
839
0
            }
840
0
            delete dumper;
841
0
            dumper = NULL;
842
0
        }
843
844
        // We need to separate the sleeping into a long interruptible sleep
845
        // and a short uninterruptible sleep. Doing this because we wake up
846
        // this thread in gflag validators. If this thread dumps just after
847
        // waking up from the condition, the gflags may not even be updated.
848
0
        const int post_sleep_ms = 50;
849
0
        int cond_sleep_ms = FLAGS_bvar_dump_interval * 1000 - post_sleep_ms;
850
0
        if (cond_sleep_ms < 0) {
851
0
            LOG(ERROR) << "Bad cond_sleep_ms=" << cond_sleep_ms;
852
0
            cond_sleep_ms = 10000;
853
0
        }
854
0
        timespec deadline = butil::milliseconds_from_now(cond_sleep_ms);
855
0
        pthread_mutex_lock(&dump_mutex);
856
0
        pthread_cond_timedwait(&dump_cond, &dump_mutex, &deadline);
857
0
        pthread_mutex_unlock(&dump_mutex);
858
0
        usleep(post_sleep_ms * 1000);
859
0
    }
860
0
}
861
862
0
static void launch_dumping_thread() {
863
0
    pthread_t thread_id;
864
0
    int rc = pthread_create(&thread_id, NULL, dumping_thread, NULL);
865
0
    if (rc != 0) {
866
0
        LOG(FATAL) << "Fail to launch dumping thread: " << berror(rc);
867
0
        return;
868
0
    }
869
    // Detach the thread because no one would join it.
870
0
    CHECK_EQ(0, pthread_detach(thread_id));
871
0
    created_dumping_thread = true;
872
0
}
873
874
// Start dumping_thread for only once.
875
0
static bool enable_dumping_thread() {
876
0
    pthread_once(&dumping_thread_once, launch_dumping_thread);
877
0
    return created_dumping_thread; 
878
0
}
879
880
0
static bool validate_bvar_dump(const char*, bool enabled) {
881
0
    if (enabled) {
882
0
        return enable_dumping_thread();
883
0
    }
884
0
    return true;
885
0
}
886
BUTIL_VALIDATE_GFLAG(bvar_dump, validate_bvar_dump);
887
888
// validators (to make these gflags reloadable in brpc)
889
0
static bool validate_bvar_dump_interval(const char*, int32_t v) {
890
    // FIXME: -bvar_dump_interval is actually unreloadable but we need to 
891
    // check validity of it, so we still add this validator. In practice
892
    // this is just fine since people rarely have the intention of modifying
893
    // this flag at runtime.
894
0
    if (v < 1) {
895
0
        LOG(ERROR) << "Invalid bvar_dump_interval=" << v;
896
0
        return false;
897
0
    }
898
0
    return true;
899
0
}
900
BUTIL_VALIDATE_GFLAG(bvar_dump_interval, validate_bvar_dump_interval);
901
902
0
static bool wakeup_dumping_thread(const char*, const std::string&) {
903
    // We're modifying a flag, wake up dumping_thread to generate
904
    // a new file soon.
905
0
    pthread_cond_signal(&dump_cond);
906
0
    return true;
907
0
}
908
909
const bool ALLOW_UNUSED dummy_bvar_dump_file = GFLAGS_NAMESPACE::RegisterFlagValidator(
910
    &FLAGS_bvar_dump_file, wakeup_dumping_thread);
911
const bool ALLOW_UNUSED dummy_bvar_dump_filter = GFLAGS_NAMESPACE::RegisterFlagValidator(
912
    &FLAGS_bvar_dump_include, wakeup_dumping_thread);
913
const bool ALLOW_UNUSED dummy_bvar_dump_exclude = GFLAGS_NAMESPACE::RegisterFlagValidator(
914
    &FLAGS_bvar_dump_exclude, wakeup_dumping_thread);
915
const bool ALLOW_UNUSED dummy_bvar_dump_prefix = GFLAGS_NAMESPACE::RegisterFlagValidator(
916
    &FLAGS_bvar_dump_prefix, wakeup_dumping_thread);
917
const bool ALLOW_UNUSED dummy_bvar_dump_tabs = GFLAGS_NAMESPACE::RegisterFlagValidator(
918
    &FLAGS_bvar_dump_tabs, wakeup_dumping_thread);
919
920
BUTIL_VALIDATE_GFLAG(mbvar_dump, validate_bvar_dump);
921
const bool ALLOW_UNUSED dummy_mbvar_dump_prefix = GFLAGS_NAMESPACE::RegisterFlagValidator(
922
    &FLAGS_mbvar_dump_prefix, wakeup_dumping_thread);
923
const bool ALLOW_UNUSED dump_mbvar_dump_file = GFLAGS_NAMESPACE::RegisterFlagValidator(
924
    &FLAGS_mbvar_dump_file, wakeup_dumping_thread);
925
926
0
static bool validate_mbvar_dump_format(const char*, const std::string& format) {
927
0
    if (format != "common"
928
0
        && format != "prometheus") {
929
0
        LOG(ERROR) << "Invalid mbvar_dump_format=" << format;
930
0
        return false;
931
0
    }
932
933
    // We're modifying a flag, wake up dumping_thread to generate
934
    // a new file soon.
935
0
    pthread_cond_signal(&dump_cond);
936
0
    return true;
937
0
}
938
939
const bool ALLOW_UNUSED dummy_mbvar_dump_format = GFLAGS_NAMESPACE::RegisterFlagValidator(
940
    &FLAGS_mbvar_dump_format, validate_mbvar_dump_format);
941
942
8
void to_underscored_name(std::string* name, const butil::StringPiece& src) {
943
8
    name->reserve(name->size() + src.size() + 8/*just guess*/);
944
170
    for (const char* p = src.data(); p != src.data() + src.size(); ++p) {
945
162
        if (isalpha(*p)) {
946
146
            if (*p < 'a') { // upper cases
947
0
                if (p != src.data() && !isupper(p[-1]) &&
948
0
                    butil::back_char(*name) != '_') {
949
0
                    name->push_back('_');
950
0
                }
951
0
                name->push_back(*p - 'A' + 'a');
952
146
            } else {
953
146
                name->push_back(*p);
954
146
            }
955
146
        } else if (isdigit(*p)) {
956
0
            name->push_back(*p);
957
16
        } else if (name->empty() || butil::back_char(*name) != '_') {
958
16
            name->push_back('_');
959
16
        }
960
162
    }
961
8
}
962
963
// UT don't need default variables.
964
#if !defined(BVAR_NOT_LINK_DEFAULT_VARIABLES)
965
// Without these, default_variables.o are stripped.
966
// At least working in gcc 4.8
967
extern int do_link_default_variables;
968
int dummy = do_link_default_variables;
969
#endif
970
971
}  // namespace bvar