Coverage Report

Created: 2026-06-25 00:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/service/http/action/file_cache_action.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
#include "service/http/action/file_cache_action.h"
19
20
#include <glog/logging.h>
21
22
#include <algorithm>
23
#include <memory>
24
#include <shared_mutex>
25
#include <sstream>
26
#include <string>
27
#include <string_view>
28
#include <vector>
29
30
#include "common/status.h"
31
#include "io/cache/block_file_cache.h"
32
#include "io/cache/block_file_cache_factory.h"
33
#include "io/cache/file_cache_common.h"
34
#include "io/cache/fs_file_cache_storage.h"
35
#include "service/http/http_channel.h"
36
#include "service/http/http_headers.h"
37
#include "service/http/http_request.h"
38
#include "service/http/http_status.h"
39
#include "storage/olap_define.h"
40
#include "storage/tablet/tablet_meta.h"
41
#include "util/easy_json.h"
42
43
namespace doris {
44
45
constexpr static std::string_view HEADER_JSON = "application/json";
46
constexpr static std::string_view OP = "op";
47
constexpr static std::string_view SYNC = "sync";
48
constexpr static std::string_view PATH = "path";
49
constexpr static std::string_view CLEAR = "clear";
50
constexpr static std::string_view RESET = "reset";
51
constexpr static std::string_view HASH = "hash";
52
constexpr static std::string_view LIST_CACHE = "list_cache";
53
constexpr static std::string_view LIST_BASE_PATHS = "list_base_paths";
54
constexpr static std::string_view CHECK_CONSISTENCY = "check_consistency";
55
constexpr static std::string_view CAPACITY = "capacity";
56
constexpr static std::string_view RELEASE = "release";
57
constexpr static std::string_view BASE_PATH = "base_path";
58
constexpr static std::string_view RELEASED_ELEMENTS = "released_elements";
59
constexpr static std::string_view DUMP = "dump";
60
constexpr static std::string_view VALUE = "value";
61
constexpr static std::string_view RELOAD = "reload";
62
63
7
Status FileCacheAction::_handle_header(HttpRequest* req, std::string* json_metrics) {
64
7
    const std::string header_json(HEADER_JSON);
65
7
    req->add_output_header(HttpHeaders::CONTENT_TYPE, header_json.c_str());
66
7
    std::string operation = req->param(std::string(OP));
67
7
    Status st = Status::OK();
68
7
    if (operation == RELEASE) {
69
0
        size_t released = 0;
70
0
        const std::string& base_path = req->param(std::string(BASE_PATH));
71
0
        if (!base_path.empty()) {
72
0
            released = io::FileCacheFactory::instance()->try_release(base_path);
73
0
        } else {
74
0
            released = io::FileCacheFactory::instance()->try_release();
75
0
        }
76
0
        EasyJson json;
77
0
        json[std::string(RELEASED_ELEMENTS)] = released;
78
0
        *json_metrics = json.ToString();
79
7
    } else if (operation == CLEAR) {
80
3
        DBUG_EXECUTE_IF("FileCacheAction._handle_header.ignore_clear", {
81
3
            LOG_WARNING("debug point FileCacheAction._handle_header.ignore_clear");
82
3
            st = Status::OK();
83
3
            return st;
84
3
        });
85
3
        const std::string& sync = req->param(std::string(SYNC));
86
3
        const std::string& segment_path = req->param(std::string(VALUE));
87
3
        if (segment_path.empty()) {
88
2
            const bool sync_clear = to_lower(sync) == "true";
89
2
            std::string clear_msg;
90
2
            RETURN_IF_ERROR(
91
2
                    io::FileCacheFactory::instance()->clear_file_caches(sync_clear, &clear_msg));
92
2
            if (sync_clear) {
93
1
                EasyJson json;
94
1
                json["status"] = "OK";
95
1
                json["msg"] = clear_msg;
96
1
                *json_metrics = json.ToString();
97
1
            }
98
2
        } else {
99
1
            io::UInt128Wrapper hash = io::BlockFileCache::hash(segment_path);
100
1
            io::BlockFileCache* cache = io::FileCacheFactory::instance()->get_by_path(hash);
101
1
            cache->remove_if_cached_async(hash);
102
1
        }
103
4
    } else if (operation == RESET) {
104
0
        std::string capacity = req->param(std::string(CAPACITY));
105
0
        int64_t new_capacity = 0;
106
0
        bool parse = true;
107
0
        try {
108
0
            new_capacity = std::stoll(capacity);
109
0
        } catch (...) {
110
0
            parse = false;
111
0
        }
112
0
        if (!parse || new_capacity <= 0) {
113
0
            st = Status::InvalidArgument(
114
0
                    "The capacity {} failed to be parsed, the capacity needs to be in "
115
0
                    "the interval (0, INT64_MAX]",
116
0
                    capacity);
117
0
        } else {
118
0
            const std::string& path = req->param(std::string(PATH));
119
0
            auto ret = io::FileCacheFactory::instance()->reset_capacity(path, new_capacity);
120
0
            LOG(INFO) << ret;
121
0
        }
122
4
    } else if (operation == HASH) {
123
0
        const std::string& segment_path = req->param(std::string(VALUE));
124
0
        if (segment_path.empty()) {
125
0
            st = Status::InvalidArgument("missing parameter: {} is required", VALUE);
126
0
        } else {
127
0
            io::UInt128Wrapper ret = io::BlockFileCache::hash(segment_path);
128
0
            EasyJson json;
129
0
            json[std::string(HASH)] = ret.to_string();
130
0
            *json_metrics = json.ToString();
131
0
        }
132
4
    } else if (operation == LIST_CACHE) {
133
0
        const std::string& segment_path = req->param(std::string(VALUE));
134
0
        if (segment_path.empty()) {
135
0
            st = Status::InvalidArgument("missing parameter: {} is required", VALUE);
136
0
        } else {
137
0
            io::UInt128Wrapper cache_hash = io::BlockFileCache::hash(segment_path);
138
0
            std::vector<std::string> cache_files =
139
0
                    io::FileCacheFactory::instance()->get_cache_file_by_path(cache_hash);
140
0
            if (cache_files.empty()) {
141
0
                *json_metrics = "[]";
142
0
            } else {
143
0
                EasyJson json;
144
0
                std::for_each(cache_files.begin(), cache_files.end(),
145
0
                              [&json](auto& x) { json.PushBack(x); });
146
0
                *json_metrics = json.ToString();
147
0
            }
148
0
        }
149
4
    } else if (operation == DUMP) {
150
0
        io::FileCacheFactory::instance()->dump_all_caches();
151
4
    } else if (operation == LIST_BASE_PATHS) {
152
1
        auto all_cache_base_path = io::FileCacheFactory::instance()->get_base_paths();
153
1
        EasyJson json;
154
1
        std::ranges::for_each(all_cache_base_path,
155
1
                              [&json](auto& x) { json.PushBack(std::move(x)); });
156
1
        *json_metrics = json.ToString();
157
3
    } else if (operation == CHECK_CONSISTENCY) {
158
3
        const std::string& cache_base_path = req->param(std::string(BASE_PATH));
159
3
        if (cache_base_path.empty()) {
160
1
            st = Status::InvalidArgument("missing parameter: {} is required", BASE_PATH);
161
2
        } else {
162
2
            auto* block_file_cache = io::FileCacheFactory::instance()->get_by_path(cache_base_path);
163
2
            if (block_file_cache == nullptr) {
164
1
                st = Status::InvalidArgument("file cache not found for base_path: {}",
165
1
                                             cache_base_path);
166
1
            } else {
167
1
                std::vector<std::string> inconsistencies;
168
1
                RETURN_IF_ERROR(block_file_cache->report_file_cache_inconsistency(inconsistencies));
169
1
                EasyJson json;
170
1
                std::ranges::for_each(inconsistencies,
171
1
                                      [&json](auto& x) { json.PushBack(std::move(x)); });
172
1
                *json_metrics = json.ToString();
173
1
            }
174
2
        }
175
3
    } else if (operation == RELOAD) {
176
0
#ifdef BE_TEST
177
0
        std::string doris_home = getenv("DORIS_HOME");
178
0
        std::string conffile = std::string(doris_home) + "/conf/be.conf";
179
0
        if (!doris::config::init(conffile.c_str(), true, true, true)) {
180
0
            return Status::InternalError("Error reading config file");
181
0
        }
182
183
0
        std::string custom_conffile = doris::config::custom_config_dir + "/be_custom.conf";
184
0
        if (!doris::config::init(custom_conffile.c_str(), true, false, false)) {
185
0
            return Status::InternalError("Error reading custom config file");
186
0
        }
187
188
0
        if (!doris::config::enable_file_cache) {
189
0
            return Status::InternalError("config::enbale_file_cache should be true!");
190
0
        }
191
192
0
        std::unordered_set<std::string> cache_path_set;
193
0
        std::vector<doris::CachePath> cache_paths;
194
0
        RETURN_IF_ERROR(doris::parse_conf_cache_paths(doris::config::file_cache_path, cache_paths));
195
196
0
        std::vector<CachePath> cache_paths_no_dup;
197
0
        cache_paths_no_dup.reserve(cache_paths.size());
198
0
        for (const auto& cache_path : cache_paths) {
199
0
            if (cache_path_set.contains(cache_path.path)) {
200
0
                LOG(WARNING) << fmt::format("cache path {} is duplicate", cache_path.path);
201
0
                continue;
202
0
            }
203
0
            cache_path_set.emplace(cache_path.path);
204
0
            cache_paths_no_dup.emplace_back(cache_path);
205
0
        }
206
0
        RETURN_IF_ERROR(doris::io::FileCacheFactory::instance()->reload_file_cache(cache_paths));
207
#else
208
        return Status::InternalError("Do not use reload in production environment!!!!");
209
#endif
210
0
    } else {
211
0
        st = Status::InternalError("invalid operation: {}", operation);
212
0
    }
213
7
    return st;
214
7
}
215
216
0
void FileCacheAction::handle(HttpRequest* req) {
217
0
    std::string json_metrics;
218
0
    Status status = _handle_header(req, &json_metrics);
219
0
    std::string status_result = status.to_json();
220
0
    if (status.ok()) {
221
0
        HttpChannel::send_reply(req, HttpStatus::OK,
222
0
                                json_metrics.empty() ? status.to_json() : json_metrics);
223
0
    } else {
224
0
        const auto http_status = status.is<ErrorCode::INVALID_ARGUMENT>()
225
0
                                         ? HttpStatus::BAD_REQUEST
226
0
                                         : HttpStatus::INTERNAL_SERVER_ERROR;
227
0
        HttpChannel::send_reply(req, http_status, status_result);
228
0
    }
229
0
}
230
231
} // namespace doris