Coverage Report

Created: 2025-10-12 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/brpc/src/bvar/mvariable.cpp
Line
Count
Source
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: 2021/11/17 14:37:53
19
20
#include <gflags/gflags.h>
21
#include <gflags/gflags_declare.h>
22
#include "butil/logging.h"                       // LOG
23
#include "butil/errno.h"                         // berror
24
#include "butil/containers/flat_map.h"           // butil::FlatMap
25
#include "butil/scoped_lock.h"                   // BAIDU_SCOPE_LOCK
26
#include "butil/file_util.h"                     // butil::FilePath
27
#include "butil/reloadable_flags.h"
28
#include "bvar/variable.h"
29
#include "bvar/mvariable.h"
30
31
namespace bvar {
32
33
DECLARE_bool(bvar_abort_on_same_name);
34
35
extern bool s_bvar_may_abort;
36
37
0
static bool validator_bvar_max_multi_dimension_metric_number(const char*, int32_t v) {
38
0
    if (v < 1) {
39
0
        LOG(ERROR) << "Invalid bvar_max_multi_dimension_metric_number=" << v;
40
0
        return false;
41
0
    }
42
0
    return true;
43
0
}
44
45
DEFINE_int32(bvar_max_multi_dimension_metric_number, 1024, "Max number of multi dimension");
46
BUTIL_VALIDATE_GFLAG(bvar_max_multi_dimension_metric_number,
47
                     validator_bvar_max_multi_dimension_metric_number);
48
49
0
static bool validator_bvar_max_dump_multi_dimension_metric_number(const char*, int32_t v) {
50
0
    if (v < 0) {
51
0
        LOG(ERROR) << "Invalid bvar_max_dump_multi_dimension_metric_number=" << v;
52
0
        return false;
53
0
    }
54
0
    return true;
55
0
}
56
DEFINE_int32(bvar_max_dump_multi_dimension_metric_number, 1024,
57
             "Max number of multi dimension metric number to dump by prometheus rpc service");
58
BUTIL_VALIDATE_GFLAG(bvar_max_dump_multi_dimension_metric_number,
59
                     validator_bvar_max_dump_multi_dimension_metric_number);
60
61
0
static bool validator_max_multi_dimension_stats_count(const char*, uint32_t v) {
62
0
    if (v < 1) {
63
0
        LOG(ERROR) << "Invalid max_multi_dimension_stats_count=" << v;
64
0
        return false;
65
0
    }
66
0
    return true;
67
0
}
68
DEFINE_uint32(max_multi_dimension_stats_count, 20000, "Max stats count of a multi dimension metric.");
69
BUTIL_VALIDATE_GFLAG(max_multi_dimension_stats_count,
70
                     validator_max_multi_dimension_stats_count);
71
72
class MVarEntry {
73
public:
74
0
    MVarEntry() : var(NULL) {}
75
76
    MVariableBase* var;
77
};
78
79
typedef butil::FlatMap<std::string, MVarEntry> MVarMap;
80
81
struct MVarMapWithLock : public MVarMap {
82
    pthread_mutex_t mutex;
83
84
0
    MVarMapWithLock() {
85
0
        if (init(256) != 0) {
86
0
            LOG(WARNING) << "Fail to init";
87
0
        }
88
0
        pthread_mutex_init(&mutex, NULL);
89
0
    }
90
};
91
92
// We have to initialize global map on need because bvar is possibly used
93
// before main().
94
static pthread_once_t s_mvar_map_once = PTHREAD_ONCE_INIT;
95
static MVarMapWithLock* s_mvar_map = NULL;
96
97
0
static void init_mvar_map() {
98
    // It's probably slow to initialize all sub maps, but rpc often expose 
99
    // variables before user. So this should not be an issue to users.
100
0
    s_mvar_map = new MVarMapWithLock();
101
0
}
102
103
0
inline MVarMapWithLock& get_mvar_map() {
104
0
    pthread_once(&s_mvar_map_once, init_mvar_map);
105
0
    return *s_mvar_map;
106
0
}
107
108
0
MVariableBase::~MVariableBase() {
109
0
    CHECK(!hide()) << "Subclass of MVariableBase MUST call hide() manually in their "
110
0
                      "dtors to avoid displaying a variable that is just destructing";
111
0
}
112
113
0
std::string MVariableBase::get_description() {
114
0
    std::ostringstream os;
115
0
    describe(os);
116
0
    return os.str();
117
0
}
118
119
int MVariableBase::describe_exposed(const std::string& name,
120
0
                                std::ostream& os) {
121
0
    MVarMapWithLock& m = get_mvar_map();
122
0
    BAIDU_SCOPED_LOCK(m.mutex);
123
0
    MVarEntry* entry = m.seek(name);
124
0
    if (entry == NULL) {
125
0
        return -1;
126
0
    }
127
0
    entry->var->describe(os);
128
0
    return 0;
129
0
}
130
131
0
std::string MVariableBase::describe_exposed(const std::string& name) {
132
0
    std::ostringstream oss;
133
0
    if (describe_exposed(name, oss) == 0) {
134
0
        return oss.str();
135
0
    }
136
0
    return std::string();
137
0
}
138
139
int MVariableBase::expose_impl(const butil::StringPiece& prefix,
140
0
                               const butil::StringPiece& name) {
141
0
    if (name.empty()) {
142
0
        LOG(ERROR) << "Parameter[name] is empty";
143
0
        return -1;
144
0
    }
145
    // NOTE: It's impossible to atomically erase from a submap and insert into
146
    // another submap without a global lock. When the to-be-exposed name
147
    // already exists, there's a chance that we can't insert back previous
148
    // name. But it should be fine generally because users are unlikely to
149
    // expose a variable more than once and calls to expose() are unlikely
150
    // to contend heavily.
151
152
    // remove previous pointer from the map if needed.
153
0
    hide();
154
    
155
    // Build the name.
156
0
    _name.clear();
157
0
    _name.reserve((prefix.size() + name.size()) * 5 / 4);
158
0
    if (!prefix.empty()) {
159
0
        to_underscored_name(&_name, prefix);
160
0
        if (!_name.empty() && butil::back_char(_name) != '_') {
161
0
            _name.push_back('_');
162
0
        }     
163
0
    }
164
0
    to_underscored_name(&_name, name);
165
   
166
0
    if (count_exposed() > (size_t)FLAGS_bvar_max_multi_dimension_metric_number) {
167
0
        LOG(ERROR) << "Too many metric seen, overflow detected, max metric count:" << FLAGS_bvar_max_multi_dimension_metric_number;
168
0
        return -1;
169
0
    }
170
171
0
    MVarMapWithLock& m = get_mvar_map();
172
0
    {
173
0
        BAIDU_SCOPED_LOCK(m.mutex);
174
0
        MVarEntry* entry = m.seek(_name);
175
0
        if (entry == NULL) {
176
0
            entry = &m[_name];
177
0
            entry->var = this;
178
0
            return 0;
179
0
        }
180
0
    }
181
182
0
    RELEASE_ASSERT_VERBOSE(!FLAGS_bvar_abort_on_same_name,
183
0
                           "Abort due to name conflict");
184
0
    if (!s_bvar_may_abort) {
185
        // Mark name conflict occurs, If this conflict happens before
186
        // initialization of bvar_abort_on_same_name, the validator will
187
        // abort the program if needed.
188
0
        s_bvar_may_abort = true;
189
0
    }
190
191
0
    LOG(WARNING) << "Already exposed `" << _name << "' whose describe is`"
192
0
               << get_description() << "'";
193
0
    _name.clear();
194
0
    return 0;
195
0
}
196
197
0
bool MVariableBase::hide() {
198
0
    if (_name.empty()) {
199
0
        return false;
200
0
    }
201
202
0
    MVarMapWithLock& m = get_mvar_map();
203
0
    BAIDU_SCOPED_LOCK(m.mutex);
204
0
    MVarEntry* entry = m.seek(_name);
205
0
    if (entry) {
206
0
        CHECK_EQ(1UL, m.erase(_name));
207
0
    } else {
208
0
        CHECK(false) << "`" << _name << "' must exist";
209
0
    }
210
0
    _name.clear();
211
0
    return true;
212
0
}
213
214
#ifdef UNIT_TEST
215
0
void MVariableBase::hide_all() {
216
0
    MVarMapWithLock& m = get_mvar_map();
217
0
    BAIDU_SCOPED_LOCK(m.mutex);
218
0
    m.clear();
219
0
}
220
#endif // end UNIT_TEST
221
222
0
size_t MVariableBase::count_exposed() {
223
0
    MVarMapWithLock& m = get_mvar_map();
224
0
    BAIDU_SCOPED_LOCK(m.mutex);
225
0
    return m.size();
226
0
}
227
228
0
void MVariableBase::list_exposed(std::vector<std::string>* names) {
229
0
    if (names == NULL) {
230
0
        return;
231
0
    }
232
233
0
    names->clear();
234
235
0
    MVarMapWithLock& mvar_map = get_mvar_map();
236
0
    BAIDU_SCOPED_LOCK(mvar_map.mutex);
237
0
    names->reserve(mvar_map.size());
238
0
    for (MVarMap::const_iterator it = mvar_map.begin(); it != mvar_map.end(); ++it) {
239
0
        names->push_back(it->first);
240
0
    }
241
0
}
242
243
0
size_t MVariableBase::dump_exposed(Dumper* dumper, const DumpOptions* options) {
244
0
    if (NULL == dumper) {
245
0
        LOG(ERROR) << "Parameter[dumper] is NULL";
246
0
        return -1;
247
0
    }
248
0
    DumpOptions opt;
249
0
    if (options) {
250
0
        opt = *options;
251
0
    }
252
0
    std::vector<std::string> mvars;
253
0
    list_exposed(&mvars);
254
0
    size_t n = 0;
255
0
    for (auto& mvar : mvars) {
256
0
        MVarMapWithLock& m = get_mvar_map();
257
0
        BAIDU_SCOPED_LOCK(m.mutex);
258
0
        MVarEntry* entry = m.seek(mvar);
259
0
        if (entry) {
260
0
            n += entry->var->dump(dumper, &opt);
261
0
        }
262
0
        if (n > static_cast<size_t>(FLAGS_bvar_max_dump_multi_dimension_metric_number)) {
263
0
            LOG(WARNING) << "truncated because of exceed max dump multi dimension label number["
264
0
                         << FLAGS_bvar_max_dump_multi_dimension_metric_number << "]";
265
0
            break;
266
0
        }
267
0
    }
268
0
    return n;
269
0
}
270
271
} // namespace bvar