Coverage Report

Created: 2026-03-16 17:53

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
4
Status FileCacheAction::_handle_header(HttpRequest* req, std::string* json_metrics) {
64
4
    const std::string header_json(HEADER_JSON);
65
4
    req->add_output_header(HttpHeaders::CONTENT_TYPE, header_json.c_str());
66
4
    std::string operation = req->param(std::string(OP));
67
4
    Status st = Status::OK();
68
4
    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
4
    } else if (operation == CLEAR) {
80
0
        DBUG_EXECUTE_IF("FileCacheAction._handle_header.ignore_clear", {
81
0
            LOG_WARNING("debug point FileCacheAction._handle_header.ignore_clear");
82
0
            st = Status::OK();
83
0
            return st;
84
0
        });
85
0
        const std::string& sync = req->param(std::string(SYNC));
86
0
        const std::string& segment_path = req->param(std::string(VALUE));
87
0
        if (segment_path.empty()) {
88
0
            io::FileCacheFactory::instance()->clear_file_caches(to_lower(sync) == "true");
89
0
        } else {
90
0
            io::UInt128Wrapper hash = io::BlockFileCache::hash(segment_path);
91
0
            io::BlockFileCache* cache = io::FileCacheFactory::instance()->get_by_path(hash);
92
0
            cache->remove_if_cached(hash);
93
0
        }
94
4
    } else if (operation == RESET) {
95
0
        std::string capacity = req->param(std::string(CAPACITY));
96
0
        int64_t new_capacity = 0;
97
0
        bool parse = true;
98
0
        try {
99
0
            new_capacity = std::stoll(capacity);
100
0
        } catch (...) {
101
0
            parse = false;
102
0
        }
103
0
        if (!parse || new_capacity <= 0) {
104
0
            st = Status::InvalidArgument(
105
0
                    "The capacity {} failed to be parsed, the capacity needs to be in "
106
0
                    "the interval (0, INT64_MAX]",
107
0
                    capacity);
108
0
        } else {
109
0
            const std::string& path = req->param(std::string(PATH));
110
0
            auto ret = io::FileCacheFactory::instance()->reset_capacity(path, new_capacity);
111
0
            LOG(INFO) << ret;
112
0
        }
113
4
    } else if (operation == HASH) {
114
0
        const std::string& segment_path = req->param(std::string(VALUE));
115
0
        if (segment_path.empty()) {
116
0
            st = Status::InvalidArgument("missing parameter: {} is required", VALUE);
117
0
        } else {
118
0
            io::UInt128Wrapper ret = io::BlockFileCache::hash(segment_path);
119
0
            EasyJson json;
120
0
            json[std::string(HASH)] = ret.to_string();
121
0
            *json_metrics = json.ToString();
122
0
        }
123
4
    } else if (operation == LIST_CACHE) {
124
0
        const std::string& segment_path = req->param(std::string(VALUE));
125
0
        if (segment_path.empty()) {
126
0
            st = Status::InvalidArgument("missing parameter: {} is required", VALUE);
127
0
        } else {
128
0
            io::UInt128Wrapper cache_hash = io::BlockFileCache::hash(segment_path);
129
0
            std::vector<std::string> cache_files =
130
0
                    io::FileCacheFactory::instance()->get_cache_file_by_path(cache_hash);
131
0
            if (cache_files.empty()) {
132
0
                *json_metrics = "[]";
133
0
            } else {
134
0
                EasyJson json;
135
0
                std::for_each(cache_files.begin(), cache_files.end(),
136
0
                              [&json](auto& x) { json.PushBack(x); });
137
0
                *json_metrics = json.ToString();
138
0
            }
139
0
        }
140
4
    } else if (operation == DUMP) {
141
0
        io::FileCacheFactory::instance()->dump_all_caches();
142
4
    } else if (operation == LIST_BASE_PATHS) {
143
1
        auto all_cache_base_path = io::FileCacheFactory::instance()->get_base_paths();
144
1
        EasyJson json;
145
1
        std::ranges::for_each(all_cache_base_path,
146
1
                              [&json](auto& x) { json.PushBack(std::move(x)); });
147
1
        *json_metrics = json.ToString();
148
3
    } else if (operation == CHECK_CONSISTENCY) {
149
3
        const std::string& cache_base_path = req->param(std::string(BASE_PATH));
150
3
        if (cache_base_path.empty()) {
151
1
            st = Status::InvalidArgument("missing parameter: {} is required", BASE_PATH);
152
2
        } else {
153
2
            auto* block_file_cache = io::FileCacheFactory::instance()->get_by_path(cache_base_path);
154
2
            if (block_file_cache == nullptr) {
155
1
                st = Status::InvalidArgument("file cache not found for base_path: {}",
156
1
                                             cache_base_path);
157
1
            } else {
158
1
                std::vector<std::string> inconsistencies;
159
1
                RETURN_IF_ERROR(block_file_cache->report_file_cache_inconsistency(inconsistencies));
160
1
                EasyJson json;
161
1
                std::ranges::for_each(inconsistencies,
162
1
                                      [&json](auto& x) { json.PushBack(std::move(x)); });
163
1
                *json_metrics = json.ToString();
164
1
            }
165
2
        }
166
3
    } else if (operation == RELOAD) {
167
0
#ifdef BE_TEST
168
0
        std::string doris_home = getenv("DORIS_HOME");
169
0
        std::string conffile = std::string(doris_home) + "/conf/be.conf";
170
0
        if (!doris::config::init(conffile.c_str(), true, true, true)) {
171
0
            return Status::InternalError("Error reading config file");
172
0
        }
173
174
0
        std::string custom_conffile = doris::config::custom_config_dir + "/be_custom.conf";
175
0
        if (!doris::config::init(custom_conffile.c_str(), true, false, false)) {
176
0
            return Status::InternalError("Error reading custom config file");
177
0
        }
178
179
0
        if (!doris::config::enable_file_cache) {
180
0
            return Status::InternalError("config::enbale_file_cache should be true!");
181
0
        }
182
183
0
        std::unordered_set<std::string> cache_path_set;
184
0
        std::vector<doris::CachePath> cache_paths;
185
0
        RETURN_IF_ERROR(doris::parse_conf_cache_paths(doris::config::file_cache_path, cache_paths));
186
187
0
        std::vector<CachePath> cache_paths_no_dup;
188
0
        cache_paths_no_dup.reserve(cache_paths.size());
189
0
        for (const auto& cache_path : cache_paths) {
190
0
            if (cache_path_set.contains(cache_path.path)) {
191
0
                LOG(WARNING) << fmt::format("cache path {} is duplicate", cache_path.path);
192
0
                continue;
193
0
            }
194
0
            cache_path_set.emplace(cache_path.path);
195
0
            cache_paths_no_dup.emplace_back(cache_path);
196
0
        }
197
0
        RETURN_IF_ERROR(doris::io::FileCacheFactory::instance()->reload_file_cache(cache_paths));
198
#else
199
        return Status::InternalError("Do not use reload in production environment!!!!");
200
#endif
201
0
    } else {
202
0
        st = Status::InternalError("invalid operation: {}", operation);
203
0
    }
204
4
    return st;
205
4
}
206
207
0
void FileCacheAction::handle(HttpRequest* req) {
208
0
    std::string json_metrics;
209
0
    Status status = _handle_header(req, &json_metrics);
210
0
    std::string status_result = status.to_json();
211
0
    if (status.ok()) {
212
0
        HttpChannel::send_reply(req, HttpStatus::OK,
213
0
                                json_metrics.empty() ? status.to_json() : json_metrics);
214
0
    } else {
215
0
        HttpChannel::send_reply(req, HttpStatus::INTERNAL_SERVER_ERROR, status_result);
216
0
    }
217
0
}
218
219
} // namespace doris