Coverage Report

Created: 2026-09-24 17:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/storage/adaptive_thread_pool_controller.h
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
#pragma once
19
20
#include <bthread/unstable.h>
21
22
#include <atomic>
23
#include <cstdint>
24
#include <functional>
25
#include <map>
26
#include <mutex>
27
#include <string>
28
#include <vector>
29
30
namespace doris {
31
32
class ThreadPool;
33
class SystemMetrics;
34
class AdaptiveThreadPoolController;
35
36
// Each pool group's timer state. Heap-allocated; shared between the controller
37
// and the brpc TimerThread callback.
38
struct TimerArg {
39
    AdaptiveThreadPoolController* ctrl; // never null
40
    std::string name;
41
    int64_t interval_ms;
42
43
    // Set before cancel() acquires mu, preventing further adjustment/re-registration.
44
    std::atomic<bool> stopped {false};
45
46
    // Updated and read under mu, including the initial registration in add().
47
    std::atomic<bthread_timer_t> timer_id {0};
48
49
    // Serializes initial registration, adjustment, re-registration and cancellation.
50
    // Taking this lock alone does not join a callback that has not acquired it yet.
51
    std::mutex mu;
52
};
53
54
// AdaptiveThreadPoolController dynamically adjusts thread pool sizes based on
55
// system load (IO utilisation, CPU utilisation, flush queue depth).
56
//
57
// Each registered pool group runs as a one-shot bthread_timer_add chain: the
58
// callback fires, adjusts the pool, then re-registers the next one-shot timer.
59
// All groups share the single brpc TimerThread, keeping the overhead minimal.
60
//
61
// Usage:
62
//   AdaptiveThreadPoolController ctrl;
63
//   ctrl.init(system_metrics, s3_pool);
64
//   ctrl.add("flush", {pool1, pool2},
65
//       AdaptiveThreadPoolController::make_flush_adjust_func(&ctrl, pool1),
66
//       max_per_cpu, min_per_cpu);
67
//   // ... later ...
68
//   ctrl.cancel("flush");   // or ctrl.stop()
69
class AdaptiveThreadPoolController {
70
public:
71
    using AdjustFunc =
72
            std::function<int(int current, int min_threads, int max_threads, std::string& reason)>;
73
74
    static constexpr int kDefaultIntervalMs = 10000;
75
76
    static constexpr int kQueueThreshold = 10;
77
    static constexpr int kIOBusyThresholdPercent = 90;
78
    static constexpr int kCPUBusyThresholdPercent = 90;
79
    static constexpr int kS3QueueBusyThreshold = 10;
80
81
1.00k
    AdaptiveThreadPoolController() = default;
82
1.00k
    ~AdaptiveThreadPoolController() { stop(); }
83
84
    // Initialize with system-level dependencies.
85
    void init(SystemMetrics* system_metrics, ThreadPool* s3_file_upload_pool);
86
87
    // Permanently stop registration and cancel all groups before pools are destroyed.
88
    void stop();
89
90
    // Register a timer chain, draining an existing registration with the same name.
91
    // Lifecycle methods must not be called from an AdjustFunc.
92
    void add(std::string name, std::vector<ThreadPool*> pools, AdjustFunc adjust_func,
93
             double max_threads_per_cpu, double min_threads_per_cpu,
94
             int64_t interval_ms = kDefaultIntervalMs);
95
96
    // Cancel the timer chain and remove the pool group. Blocks until any
97
    // in-flight callback finishes, then returns. Safe to call before pool teardown.
98
    void cancel(const std::string& name);
99
100
    // Fire all registered groups once, ignoring the schedule. For testing.
101
    void adjust_once();
102
103
    // Get current thread count for a named group. For testing/debugging.
104
    int get_current_threads(const std::string& name) const;
105
106
    // System-state helpers; safe to call from inside an AdjustFunc.
107
    bool is_io_busy();
108
    bool is_cpu_busy();
109
110
    // Factory: standard flush-pool adjust function.
111
    static AdjustFunc make_flush_adjust_func(AdaptiveThreadPoolController* controller,
112
                                             ThreadPool* flush_pool);
113
114
    // Callback registered with bthread_timer_add. Public only for the C linkage
115
    // requirement; do not call directly.
116
    static void _on_timer(void* arg);
117
118
private:
119
    struct PoolGroup {
120
        std::string name;
121
        std::vector<ThreadPool*> pools;
122
        AdjustFunc adjust_func;
123
        double max_threads_per_cpu = 4.0;
124
        double min_threads_per_cpu = 0.5;
125
        int current_threads = 0;
126
        TimerArg* timer_arg = nullptr; // owned; freed by cancel()
127
128
        int get_max_threads() const;
129
        int get_min_threads() const;
130
    };
131
132
    // Run one group's adjustment. Called from _on_timer (no lock on entry).
133
    void _fire_group(const std::string& name);
134
135
    void _apply_thread_count(PoolGroup& group, int target_threads, const std::string& reason);
136
137
    // Requires _lifecycle_mutex.
138
    void _cancel(const std::string& name);
139
140
private:
141
    SystemMetrics* _system_metrics = nullptr;
142
    ThreadPool* _s3_file_upload_pool = nullptr;
143
144
    // Serializes add/cancel/stop so concurrent teardown also waits for cancellation.
145
    std::mutex _lifecycle_mutex;
146
    bool _stopped = false;
147
    mutable std::mutex _mutex;
148
    mutable std::mutex _metrics_state_mutex;
149
    std::map<std::string, PoolGroup> _pool_groups;
150
151
    // Last successfully computed IO-busy result. Returned as-is when the
152
    // measurement interval is too short to produce a valid new delta.
153
    bool _last_io_busy = false;
154
155
    // For disk IO util calculation (used by is_io_busy).
156
    std::map<std::string, int64_t> _last_disk_io_time;
157
    int64_t _last_check_time_sec = 0;
158
159
    // For CPU util calculation (used by is_cpu_busy). The counters come from
160
    // SystemMetrics' existing cpu_* metrics and are compared as deltas.
161
    bool _last_cpu_busy = false;
162
    int64_t _last_cpu_total_time = -1;
163
    int64_t _last_cpu_idle_time = -1;
164
};
165
166
} // namespace doris