Coverage Report

Created: 2026-01-06 15:08

/root/doris/cloud/src/common/metric.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
#include "metric.h"
19
20
#include <glog/logging.h>
21
#include <rapidjson/document.h>
22
#include <rapidjson/encodings.h>
23
#include <rapidjson/error/en.h>
24
25
#include <cstdint>
26
#include <memory>
27
#include <optional>
28
#include <set>
29
#include <string>
30
#include <string_view>
31
#include <unordered_map>
32
#include <vector>
33
34
#include "common/bvars.h"
35
#include "common/logging.h"
36
#include "meta-store/keys.h"
37
#include "meta-store/txn_kv.h"
38
#include "meta-store/txn_kv_error.h"
39
40
namespace doris::cloud {
41
extern std::set<std::string> get_key_prefix_contants();
42
43
// The format of the output is shown in "test/fdb_metric_example.json"
44
static const std::string FDB_STATUS_KEY = "\xff\xff/status/json";
45
46
17
static std::string get_fdb_status(TxnKv* txn_kv) {
47
17
    std::unique_ptr<Transaction> txn;
48
17
    TxnErrorCode err = txn_kv->create_txn(&txn);
49
17
    if (err != TxnErrorCode::TXN_OK) {
50
0
        LOG(WARNING) << "failed to create_txn, err=" << err;
51
0
        return "";
52
0
    }
53
17
    std::string status_val;
54
17
    err = txn->get(FDB_STATUS_KEY, &status_val);
55
17
    if (err != TxnErrorCode::TXN_OK) {
56
9
        LOG(WARNING) << "failed to get FDB_STATUS_KEY, err=" << err;
57
9
        return "";
58
9
    }
59
8
    return status_val;
60
17
}
61
62
// The format of fdb status details:
63
//
64
// Configuration:
65
//   Redundancy mode        - double
66
//   Storage engine         - ssd-2
67
//   Coordinators           - 3
68
//   Usable Regions         - 1
69
70
// Cluster:
71
//   FoundationDB processes - 15
72
//   Zones                  - 3
73
//   Machines               - 3
74
//   Memory availability    - 2.9 GB per process on machine with least available
75
//                            >>>>> (WARNING: 4.0 GB recommended) <<<<<
76
//   Retransmissions rate   - 3 Hz
77
//   Fault Tolerance        - 1 machines
78
//   Server time            - 02/16/23 16:48:14
79
80
// Data:
81
//   Replication health     - Healthy
82
//   Moving data            - 0.000 GB
83
//   Sum of key-value sizes - 4.317 GB
84
//   Disk space used        - 11.493 GB
85
86
// Operating space:
87
//   Storage server         - 462.8 GB free on most full server
88
//   Log server             - 462.8 GB free on most full server
89
90
// Workload:
91
//   Read rate              - 84 Hz
92
//   Write rate             - 4 Hz
93
//   Transactions started   - 222 Hz
94
//   Transactions committed - 4 Hz
95
//   Conflict rate          - 0 Hz
96
97
// Backup and DR:
98
//   Running backups        - 0
99
//   Running DRs            - 0
100
101
17
static void export_fdb_status_details(const std::string& status_str) {
102
17
    using namespace rapidjson;
103
17
    Document document;
104
17
    try {
105
17
        document.Parse(status_str.c_str());
106
17
        if (document.HasParseError()) {
107
9
            LOG(WARNING) << "fail to parse status str, err: "
108
9
                         << GetParseError_En(document.GetParseError());
109
9
            return;
110
9
        }
111
17
    } catch (std::exception& e) {
112
0
        LOG(WARNING) << "fail to parse status str, err: " << e.what();
113
0
        return;
114
0
    }
115
116
8
    if (!document.HasMember("cluster") || !document.HasMember("client")) {
117
2
        LOG(WARNING) << "err fdb status details";
118
2
        return;
119
2
    }
120
126
    auto get_value = [&](const std::vector<const char*>& v) -> int64_t {
121
126
        if (v.empty()) return BVAR_FDB_INVALID_VALUE;
122
126
        auto node = document.FindMember("cluster");
123
258
        for (const auto& name : v) {
124
258
            if (!node->value.HasMember(name)) return BVAR_FDB_INVALID_VALUE;
125
256
            node = node->value.FindMember(name);
126
256
        }
127
124
        if (node->value.IsInt64()) return node->value.GetInt64();
128
16
        if (node->value.IsDouble()) return static_cast<int64_t>(node->value.GetDouble());
129
16
        if (node->value.IsObject()) return node->value.MemberCount();
130
6
        if (node->value.IsArray()) return node->value.Size();
131
0
        return BVAR_FDB_INVALID_VALUE;
132
6
    };
133
30
    auto get_nanoseconds = [&](const std::vector<const char*>& v) -> int64_t {
134
30
        constexpr double NANOSECONDS = 1e9;
135
30
        auto node = document.FindMember("cluster");
136
72
        for (const auto& name : v) {
137
72
            if (!node->value.HasMember(name)) return BVAR_FDB_INVALID_VALUE;
138
72
            node = node->value.FindMember(name);
139
72
        }
140
30
        if (node->value.IsInt64()) return node->value.GetInt64() * NANOSECONDS;
141
24
        DCHECK(node->value.IsDouble());
142
24
        return static_cast<int64_t>(node->value.GetDouble() * NANOSECONDS);
143
30
    };
144
    // Configuration
145
6
    g_bvar_fdb_configuration_coordinators_count.set_value(
146
6
            get_value({"configuration", "coordinators_count"}));
147
6
    g_bvar_fdb_configuration_usable_regions.set_value(
148
6
            get_value({"configuration", "usable_regions"}));
149
150
    // Cluster
151
6
    g_bvar_fdb_process_count.set_value(get_value({"processes"}));
152
6
    g_bvar_fdb_machines_count.set_value(get_value({"machines"}));
153
6
    g_bvar_fdb_fault_tolerance_count.set_value(
154
6
            get_value({"fault_tolerance", "max_zone_failures_without_losing_data"}));
155
6
    g_bvar_fdb_generation.set_value(get_value({"generation"}));
156
6
    g_bvar_fdb_incompatible_connections.set_value(get_value({"incompatible_connections"}));
157
158
    // Data/Operating space
159
6
    g_bvar_fdb_data_average_partition_size_bytes.set_value(
160
6
            get_value({"data", "average_partition_size_bytes"}));
161
6
    g_bvar_fdb_data_partition_count.set_value(get_value({"data", "partitions_count"}));
162
6
    g_bvar_fdb_data_total_disk_used_bytes.set_value(get_value({"data", "total_disk_used_bytes"}));
163
6
    g_bvar_fdb_data_total_kv_size_bytes.set_value(get_value({"data", "total_kv_size_bytes"}));
164
6
    g_bvar_fdb_data_log_server_space_bytes.set_value(
165
6
            get_value({"data", "least_operating_space_bytes_log_server"}));
166
6
    g_bvar_fdb_data_storage_server_space_bytes.set_value(
167
6
            get_value({"data", "least_operating_space_bytes_storage_server"}));
168
6
    g_bvar_fdb_data_moving_data_highest_priority.set_value(
169
6
            get_value({"data", "moving_data", "highest_priority"}));
170
6
    g_bvar_fdb_data_moving_data_in_flight_bytes.set_value(
171
6
            get_value({"data", "moving_data", "in_flight_bytes"}));
172
6
    g_bvar_fdb_data_moving_data_in_queue_bytes.set_value(
173
6
            get_value({"data", "moving_data", "in_queue_bytes"}));
174
6
    g_bvar_fdb_data_moving_total_written_bytes.set_value(
175
6
            get_value({"data", "moving_data", "total_written_bytes"}));
176
6
    g_bvar_fdb_data_state_min_replicas_remaining.set_value(
177
6
            get_value({"data", "state", "min_replicas_remaining"}));
178
179
    // Latency probe
180
6
    g_bvar_fdb_latency_probe_transaction_start_ns.set_value(
181
6
            get_nanoseconds({"latency_probe", "transaction_start_seconds"}));
182
6
    g_bvar_fdb_latency_probe_commit_ns.set_value(
183
6
            get_nanoseconds({"latency_probe", "commit_seconds"}));
184
6
    g_bvar_fdb_latency_probe_read_ns.set_value(get_nanoseconds({"latency_probe", "read_seconds"}));
185
186
    // QOS
187
6
    g_bvar_fdb_qos_worst_data_lag_storage_server_ns.set_value(
188
6
            get_nanoseconds({"qos", "worst_data_lag_storage_server", "seconds"}));
189
6
    g_bvar_fdb_qos_worst_durability_lag_storage_server_ns.set_value(
190
6
            get_nanoseconds({"qos", "worst_durability_lag_storage_server", "seconds"}));
191
6
    g_bvar_fdb_qos_worst_log_server_queue_bytes.set_value(
192
6
            get_value({"qos", "worst_queue_bytes_log_server"}));
193
6
    g_bvar_fdb_qos_worst_storage_server_queue_bytes.set_value(
194
6
            get_value({"qos", "worst_queue_bytes_storage_server"}));
195
196
    // Backup and DR
197
198
    // Client Count
199
6
    g_bvar_fdb_client_count.set_value(get_value({"clients", "count"}));
200
201
    // Coordinators Unreachable Count
202
6
    auto unreachable_count = 0;
203
6
    if (auto node = document.FindMember("client"); node->value.HasMember("coordinators")) {
204
6
        if (node = node->value.FindMember("coordinators"); node->value.HasMember("coordinators")) {
205
6
            if (node = node->value.FindMember("coordinators"); node->value.IsArray()) {
206
18
                for (const auto& c : node->value.GetArray()) {
207
18
                    if (c.HasMember("reachable") && c.FindMember("reachable")->value.IsBool() &&
208
18
                        !c.FindMember("reachable")->value.GetBool()) {
209
0
                        ++unreachable_count;
210
0
                    }
211
18
                }
212
6
                g_bvar_fdb_coordinators_unreachable_count.set_value(unreachable_count);
213
6
            }
214
6
        }
215
6
    }
216
217
    // Helper function for recursive name construction
218
    // such as: {"disk": {"reads": {"counter": 123, "hz": 0}}}
219
    // component is "disk", the names of these two values should be "reads_counter" and "reads_hz"
220
6
    auto recursive_name_helper = [](std::string& origin_name,
221
828
                                    const char* next_level_name) -> std::string {
222
828
        return origin_name + '_' + next_level_name;
223
828
    };
224
225
    // Generic recursive function to traverse JSON node and set bvar values
226
    // There are three cases here: int64, double, and object.
227
    // If it is double or int64, put it directly into the bvar.
228
    // If it is an object, recursively obtain the full name and corresponding value.
229
6
    auto recursive_traverse_and_set = [&recursive_name_helper](auto&& set_value_callback,
230
6
                                                               auto&& self, std::string name,
231
1.91k
                                                               auto temp_node) -> void {
232
        // if the node is an object, then get Member(iter) and recursive with iter as arg
233
1.91k
        if (temp_node->value.IsObject()) {
234
1.10k
            for (auto iter = temp_node->value.MemberBegin(); iter != temp_node->value.MemberEnd();
235
828
                 iter++) {
236
828
                self(set_value_callback, self, recursive_name_helper(name, iter->name.GetString()),
237
828
                     iter);
238
828
            }
239
276
            return;
240
276
        }
241
        // if not object, set bvar value
242
1.63k
        set_value_callback(name, temp_node);
243
1.63k
    };
metric.cpp:_ZZN5doris5cloudL25export_fdb_status_detailsERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEENK3$_0clIRZZNS0_L25export_fdb_status_detailsES8_ENK3$_1clES6_EUlRS6_RN9rapidjson21GenericMemberIteratorILb0ENSD_4UTF8IcEENSD_19MemoryPoolAllocatorINSD_12CrtAllocatorEEEEEE_RS9_SK_EEvOT_OT0_S6_T1_
Line
Count
Source
231
1.53k
                                                               auto temp_node) -> void {
232
        // if the node is an object, then get Member(iter) and recursive with iter as arg
233
1.53k
        if (temp_node->value.IsObject()) {
234
720
            for (auto iter = temp_node->value.MemberBegin(); iter != temp_node->value.MemberEnd();
235
540
                 iter++) {
236
540
                self(set_value_callback, self, recursive_name_helper(name, iter->name.GetString()),
237
540
                     iter);
238
540
            }
239
180
            return;
240
180
        }
241
        // if not object, set bvar value
242
1.35k
        set_value_callback(name, temp_node);
243
1.35k
    };
metric.cpp:_ZZN5doris5cloudL25export_fdb_status_detailsERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEENK3$_0clIRZZNS0_L25export_fdb_status_detailsES8_ENK3$_2clES6_EUlRS6_RN9rapidjson21GenericMemberIteratorILb0ENSD_4UTF8IcEENSD_19MemoryPoolAllocatorINSD_12CrtAllocatorEEEEEE_RS9_SK_EEvOT_OT0_S6_T1_
Line
Count
Source
231
384
                                                               auto temp_node) -> void {
232
        // if the node is an object, then get Member(iter) and recursive with iter as arg
233
384
        if (temp_node->value.IsObject()) {
234
384
            for (auto iter = temp_node->value.MemberBegin(); iter != temp_node->value.MemberEnd();
235
288
                 iter++) {
236
288
                self(set_value_callback, self, recursive_name_helper(name, iter->name.GetString()),
237
288
                     iter);
238
288
            }
239
96
            return;
240
96
        }
241
        // if not object, set bvar value
242
288
        set_value_callback(name, temp_node);
243
288
    };
244
245
18
    auto get_process_metric = [&](std::string component) {
246
18
        auto node = document.FindMember("cluster");
247
18
        if (!node->value.HasMember("processes")) return;
248
18
        node = node->value.FindMember("processes");
249
        // process
250
288
        for (auto process_node = node->value.MemberBegin(); process_node != node->value.MemberEnd();
251
270
             process_node++) {
252
270
            const char* process_id = process_node->name.GetString();
253
270
            decltype(process_node) component_node;
254
            // get component iter
255
270
            if (!process_node->value.HasMember(component.data())) continue;
256
270
            component_node = process_node->value.FindMember(component.data());
257
258
            // set_bvar_value is responsible for setting integer and float values to the corresponding bvar.
259
270
            auto set_bvar_value = [&process_id, &component](
260
270
                                          std::string& name,
261
1.35k
                                          decltype(process_node)& temp_node) -> void {
262
1.35k
                if (temp_node->value.IsInt64()) {
263
1.08k
                    g_bvar_fdb_cluster_processes.put(
264
1.08k
                            {process_id, component, name},
265
1.08k
                            static_cast<double>(temp_node->value.GetInt64()));
266
1.08k
                    return;
267
1.08k
                }
268
270
                if (temp_node->value.IsDouble()) {
269
270
                    g_bvar_fdb_cluster_processes.put({process_id, component, name},
270
270
                                                     temp_node->value.GetDouble());
271
270
                    return;
272
270
                }
273
0
                LOG(WARNING) << fmt::format(
274
0
                        "Get process metrics set_bvar_value input a wrong type node {}", name);
275
0
            };
276
            // Note that the parameter passed to set_bvar_value here is the current node, not its Member
277
            // so we can directly call recursive_traverse_and_set in the loop
278
270
            for (auto metric_node = component_node->value.MemberBegin();
279
1.26k
                 metric_node != component_node->value.MemberEnd(); metric_node++) {
280
990
                recursive_traverse_and_set(set_bvar_value, recursive_traverse_and_set,
281
990
                                           metric_node->name.GetString(), metric_node);
282
990
            }
283
270
        }
284
18
    };
285
286
24
    auto get_workload_metric = [&](std::string component) {
287
24
        auto node = document.FindMember("cluster");
288
24
        if (!node->value.HasMember("workload")) return;
289
24
        node = node->value.FindMember("workload");
290
291
24
        if (!node->value.HasMember(component.data())) return;
292
24
        auto component_node = node->value.FindMember(component.data());
293
294
        // set_bvar_value is responsible for setting integer and float values to the corresponding bvar.
295
24
        auto set_bvar_value = [&component](std::string& name,
296
288
                                           decltype(component_node)& temp_node) -> void {
297
288
            if (temp_node->value.IsInt64()) {
298
180
                g_bvar_fdb_cluster_workload.put({component, name},
299
180
                                                static_cast<double>(temp_node->value.GetInt64()));
300
180
                return;
301
180
            }
302
108
            if (temp_node->value.IsDouble()) {
303
108
                g_bvar_fdb_cluster_workload.put({component, name}, temp_node->value.GetDouble());
304
108
                return;
305
108
            }
306
0
            LOG(WARNING) << fmt::format(
307
0
                    "Get workload metrics set_bvar_value input a wrong type node {}", name);
308
0
        };
309
310
        // Reuse the common recursive_traverse_and_set function
311
24
        for (auto metric_node = component_node->value.MemberBegin();
312
120
             metric_node != component_node->value.MemberEnd(); metric_node++) {
313
96
            recursive_traverse_and_set(set_bvar_value, recursive_traverse_and_set,
314
96
                                       metric_node->name.GetString(), metric_node);
315
96
        }
316
24
    };
317
318
    // Process Status
319
6
    get_process_metric("cpu");
320
6
    get_process_metric("disk");
321
6
    get_process_metric("memory");
322
323
    // Workload Status
324
6
    get_workload_metric("keys");
325
6
    get_workload_metric("bytes");
326
6
    get_workload_metric("operations");
327
6
    get_workload_metric("transactions");
328
6
}
329
330
// boundaries include the key category{meta, txn, recycle...}, instance_id and sub_category{rowset, txn_label...}
331
// encode look like
332
// 0x01 "txn" ${instance_id} "txn_label" ${db_id} ${label}
333
// 0x01 "meta" ${instance_id} "rowset" ${tablet_id} ${version}
334
// the func count same key to hashmap kv_range_count
335
// exmaple:
336
// kv_range_boundaries: meta|instance1|rowset|..., meta|instance1|rowset|..., meta|instance2|rowset|..., txn|instance1|txn_label|...
337
// kv_range_count output: <meta|instance1|rowset, 2>, <meta|instance2|rowset, 1>, <txn|instance1|txn_label, 1>
338
void get_kv_range_boundaries_count(std::vector<std::string>& kv_range_boundaries,
339
0
                                   std::unordered_map<std::string, size_t>& kv_range_count) {
340
0
    size_t prefix_size = FdbTxnKv::fdb_partition_key_prefix().size();
341
0
    for (auto&& boundary : kv_range_boundaries) {
342
0
        if (boundary.size() < prefix_size + 1 || boundary[prefix_size] != CLOUD_USER_KEY_SPACE01) {
343
0
            continue;
344
0
        }
345
346
0
        std::string_view user_key(boundary);
347
0
        user_key.remove_prefix(prefix_size + 1); // Skip the KEY_SPACE prefix.
348
0
        std::vector<std::tuple<std::variant<int64_t, std::string>, int, int>> out;
349
0
        decode_key(&user_key, &out); // ignore any error, since the boundary key might be truncated.
350
351
0
        auto visitor = [](auto&& arg) -> std::string {
352
0
            using T = std::decay_t<decltype(arg)>;
353
0
            if constexpr (std::is_same_v<T, std::string>) {
354
0
                return arg;
355
0
            } else {
356
0
                return std::to_string(arg);
357
0
            }
358
0
        };
Unexecuted instantiation: metric.cpp:_ZZN5doris5cloud29get_kv_range_boundaries_countERSt6vectorINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESaIS7_EERSt13unordered_mapIS7_mSt4hashIS7_ESt8equal_toIS7_ESaISt4pairIKS7_mEEEENK3$_0clIRlEES7_OT_
Unexecuted instantiation: metric.cpp:_ZZN5doris5cloud29get_kv_range_boundaries_countERSt6vectorINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESaIS7_EERSt13unordered_mapIS7_mSt4hashIS7_ESt8equal_toIS7_ESaISt4pairIKS7_mEEEENK3$_0clIRS7_EES7_OT_
359
360
0
        if (!out.empty()) {
361
0
            std::string key;
362
            // whatever the boundary's category have similar encode part:
363
            // category, instance_id, sub_category
364
            // we can distinguish boundary using the three parts
365
            // some boundaries do not contain all three parts, so restrictions based on size are also necessary
366
0
            for (size_t i = 0; i < 3 && i < out.size(); ++i) {
367
0
                key += std::visit(visitor, std::get<0>(out[i])) + '|';
368
0
            }
369
0
            key.pop_back();
370
0
            kv_range_count[key]++;
371
0
        }
372
0
    }
373
0
}
374
375
17
static void export_fdb_kv_ranges_details(TxnKv* kv) {
376
17
    auto* txn_kv = dynamic_cast<FdbTxnKv*>(kv);
377
17
    if (!txn_kv) {
378
17
        LOG(WARNING) << "this method only support fdb txn kv";
379
17
        return;
380
17
    }
381
382
0
    std::vector<std::string> partition_boundaries;
383
0
    TxnErrorCode code = txn_kv->get_partition_boundaries(&partition_boundaries);
384
0
    if (code != TxnErrorCode::TXN_OK) {
385
0
        auto msg = fmt::format("failed to get boundaries, code={}", code);
386
0
        return;
387
0
    }
388
389
0
    std::unordered_map<std::string, size_t> partition_count;
390
0
    get_kv_range_boundaries_count(partition_boundaries, partition_count);
391
392
0
    auto key_prefix_set = get_key_prefix_contants();
393
0
    std::unordered_map<std::string, int64_t> category_count;
394
0
    for (auto&& [key, count] : partition_count) {
395
0
        std::vector<std::string> keys;
396
0
        size_t pos {};
397
        // split key with '|'
398
0
        do {
399
0
            size_t p = std::min(key.size(), key.find('|', pos));
400
0
            keys.emplace_back(key.substr(pos, p - pos));
401
0
            pos = p + 1;
402
0
        } while (pos < key.size());
403
0
        keys.resize(3);
404
0
        if (key_prefix_set.contains(keys[0])) {
405
0
            category_count[keys[0]] += count;
406
0
            g_bvar_fdb_kv_ranges_count.put({keys[0], keys[1], keys[2]}, count);
407
0
        } else {
408
0
            LOG(WARNING) << fmt::format("Unknow meta range type: {}", keys[0]);
409
0
            continue;
410
0
        }
411
0
    }
412
0
}
413
414
17
void FdbMetricExporter::export_fdb_metrics(TxnKv* txn_kv) {
415
17
    int64_t busyness = 0;
416
17
    std::string fdb_status = get_fdb_status(txn_kv);
417
17
    export_fdb_status_details(fdb_status);
418
17
    export_fdb_kv_ranges_details(txn_kv);
419
17
    if (auto* kv = dynamic_cast<FdbTxnKv*>(txn_kv); kv != nullptr) {
420
0
        busyness = static_cast<int64_t>(kv->get_client_thread_busyness() * 100);
421
0
        g_bvar_fdb_client_thread_busyness_percent.set_value(busyness);
422
0
    }
423
17
    LOG(INFO) << "finish to collect fdb metric, client busyness: " << busyness << "%";
424
17
}
425
426
7
FdbMetricExporter::~FdbMetricExporter() {
427
7
    stop();
428
7
}
429
430
6
int FdbMetricExporter::start() {
431
6
    if (txn_kv_ == nullptr) return -1;
432
6
    std::unique_lock lock(running_mtx_);
433
6
    if (running_) {
434
0
        return 0;
435
0
    }
436
437
6
    running_ = true;
438
6
    thread_ = std::make_unique<std::thread>([this] {
439
23
        while (running_.load(std::memory_order_acquire)) {
440
17
            export_fdb_metrics(txn_kv_.get());
441
17
            std::unique_lock l(running_mtx_);
442
17
            running_cond_.wait_for(l, std::chrono::milliseconds(sleep_interval_ms_),
443
29
                                   [this]() { return !running_.load(std::memory_order_acquire); });
444
17
        }
445
6
    });
446
6
    pthread_setname_np(thread_->native_handle(), "fdb_metrics_exporter");
447
6
    return 0;
448
6
}
449
450
13
void FdbMetricExporter::stop() {
451
13
    {
452
13
        std::unique_lock lock(running_mtx_);
453
13
        running_.store(false);
454
13
        running_cond_.notify_all();
455
13
    }
456
457
13
    if (thread_ != nullptr && thread_->joinable()) {
458
6
        thread_->join();
459
6
        thread_.reset();
460
6
    }
461
13
}
462
463
} // namespace doris::cloud