Coverage Report

Created: 2024-09-11 06:42

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