Coverage Report

Created: 2026-04-09 15:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/service/http/action/compaction_profile_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/compaction_profile_action.h"
19
20
#include <rapidjson/document.h>
21
#include <rapidjson/prettywriter.h>
22
#include <rapidjson/stringbuffer.h>
23
24
#include <ctime>
25
#include <exception>
26
#include <string>
27
28
#include "common/logging.h"
29
#include "service/http/http_channel.h"
30
#include "service/http/http_headers.h"
31
#include "service/http/http_request.h"
32
#include "service/http/http_status.h"
33
#include "storage/compaction_task_tracker.h"
34
35
namespace doris {
36
37
#include "common/compile_check_begin.h"
38
39
namespace {
40
41
// Format millisecond timestamp to "YYYY-MM-DD HH:MM:SS" string.
42
// Returns empty string for 0 timestamps.
43
0
std::string format_timestamp_ms(int64_t timestamp_ms) {
44
0
    if (timestamp_ms <= 0) {
45
0
        return "";
46
0
    }
47
0
    time_t ts = static_cast<time_t>(timestamp_ms / 1000);
48
0
    struct tm local_tm;
49
0
    if (localtime_r(&ts, &local_tm) == nullptr) {
50
0
        return "";
51
0
    }
52
0
    char buf[64];
53
0
    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &local_tm);
54
0
    return std::string(buf);
55
0
}
56
57
} // namespace
58
59
CompactionProfileAction::CompactionProfileAction(ExecEnv* exec_env, TPrivilegeHier::type hier,
60
                                                 TPrivilegeType::type ptype)
61
0
        : HttpHandlerWithAuth(exec_env, hier, ptype) {}
62
63
0
void CompactionProfileAction::handle(HttpRequest* req) {
64
0
    req->add_output_header(HttpHeaders::CONTENT_TYPE, HttpHeaders::JSON_TYPE.data());
65
66
    // Parse optional parameters
67
0
    int64_t tablet_id = 0;
68
0
    int64_t top_n = 0;
69
0
    std::string compact_type;
70
0
    int success_filter = -1; // -1 = no filter, 0 = failed only, 1 = success only
71
72
    // tablet_id
73
0
    const auto& tablet_id_str = req->param("tablet_id");
74
0
    if (!tablet_id_str.empty()) {
75
0
        try {
76
0
            tablet_id = std::stoll(tablet_id_str);
77
0
            if (tablet_id < 0) {
78
0
                HttpChannel::send_reply(req, HttpStatus::BAD_REQUEST,
79
0
                                        R"({"status":"Error","msg":"tablet_id must be >= 0"})");
80
0
                return;
81
0
            }
82
0
        } catch (const std::exception& e) {
83
0
            auto msg = R"({"status":"Error","msg":"invalid tablet_id: )" + std::string(e.what()) +
84
0
                       "\"}";
85
0
            HttpChannel::send_reply(req, HttpStatus::BAD_REQUEST, msg);
86
0
            return;
87
0
        }
88
0
    }
89
90
    // top_n
91
0
    const auto& top_n_str = req->param("top_n");
92
0
    if (!top_n_str.empty()) {
93
0
        try {
94
0
            top_n = std::stoll(top_n_str);
95
0
            if (top_n < 0) {
96
0
                HttpChannel::send_reply(req, HttpStatus::BAD_REQUEST,
97
0
                                        R"({"status":"Error","msg":"top_n must be >= 0"})");
98
0
                return;
99
0
            }
100
0
        } catch (const std::exception& e) {
101
0
            auto msg =
102
0
                    R"({"status":"Error","msg":"invalid top_n: )" + std::string(e.what()) + "\"}";
103
0
            HttpChannel::send_reply(req, HttpStatus::BAD_REQUEST, msg);
104
0
            return;
105
0
        }
106
0
    }
107
108
    // compact_type
109
0
    compact_type = req->param("compact_type");
110
0
    if (!compact_type.empty() && compact_type != "base" && compact_type != "cumulative" &&
111
0
        compact_type != "full") {
112
0
        HttpChannel::send_reply(
113
0
                req, HttpStatus::BAD_REQUEST,
114
0
                R"({"status":"Error","msg":"compact_type must be one of: base, cumulative, full"})");
115
0
        return;
116
0
    }
117
118
    // success
119
0
    const auto& success_str = req->param("success");
120
0
    if (!success_str.empty()) {
121
0
        if (success_str == "true") {
122
0
            success_filter = 1;
123
0
        } else if (success_str == "false") {
124
0
            success_filter = 0;
125
0
        } else {
126
0
            HttpChannel::send_reply(
127
0
                    req, HttpStatus::BAD_REQUEST,
128
0
                    R"({"status":"Error","msg":"success must be 'true' or 'false'"})");
129
0
            return;
130
0
        }
131
0
    }
132
133
    // Get completed tasks from tracker
134
0
    auto tasks = CompactionTaskTracker::instance()->get_completed_tasks(
135
0
            tablet_id, top_n, compact_type, success_filter);
136
137
    // Build JSON response
138
0
    rapidjson::Document root;
139
0
    root.SetObject();
140
0
    auto& allocator = root.GetAllocator();
141
142
0
    root.AddMember("status", "Success", allocator);
143
144
0
    rapidjson::Value profiles(rapidjson::kArrayType);
145
146
0
    for (const auto& task : tasks) {
147
0
        rapidjson::Value profile(rapidjson::kObjectType);
148
149
0
        profile.AddMember("compaction_id", task.compaction_id, allocator);
150
151
0
        {
152
0
            rapidjson::Value v;
153
0
            v.SetString(to_string(task.compaction_type), allocator);
154
0
            profile.AddMember("compaction_type", v, allocator);
155
0
        }
156
157
0
        profile.AddMember("tablet_id", task.tablet_id, allocator);
158
0
        profile.AddMember("table_id", task.table_id, allocator);
159
0
        profile.AddMember("partition_id", task.partition_id, allocator);
160
161
0
        {
162
0
            rapidjson::Value v;
163
0
            v.SetString(to_string(task.trigger_method), allocator);
164
0
            profile.AddMember("trigger_method", v, allocator);
165
0
        }
166
167
0
        profile.AddMember("compaction_score", task.compaction_score, allocator);
168
169
        // Datetime fields
170
0
        {
171
0
            auto s = format_timestamp_ms(task.scheduled_time_ms);
172
0
            rapidjson::Value v;
173
0
            v.SetString(s.c_str(), static_cast<rapidjson::SizeType>(s.size()), allocator);
174
0
            profile.AddMember("scheduled_time", v, allocator);
175
0
        }
176
0
        {
177
0
            auto s = format_timestamp_ms(task.start_time_ms);
178
0
            rapidjson::Value v;
179
0
            v.SetString(s.c_str(), static_cast<rapidjson::SizeType>(s.size()), allocator);
180
0
            profile.AddMember("start_time", v, allocator);
181
0
        }
182
0
        {
183
0
            auto s = format_timestamp_ms(task.end_time_ms);
184
0
            rapidjson::Value v;
185
0
            v.SetString(s.c_str(), static_cast<rapidjson::SizeType>(s.size()), allocator);
186
0
            profile.AddMember("end_time", v, allocator);
187
0
        }
188
189
        // Derived: cost_time_ms = end_time_ms - start_time_ms
190
0
        int64_t cost_time_ms = 0;
191
0
        if (task.start_time_ms > 0 && task.end_time_ms > 0) {
192
0
            cost_time_ms = task.end_time_ms - task.start_time_ms;
193
0
        }
194
0
        profile.AddMember("cost_time_ms", cost_time_ms, allocator);
195
196
        // Derived: success = (status == FINISHED)
197
0
        bool success = (task.status == CompactionTaskStatus::FINISHED);
198
0
        profile.AddMember("success", success, allocator);
199
200
        // Input statistics
201
0
        profile.AddMember("input_rowsets_count", task.input_rowsets_count, allocator);
202
0
        profile.AddMember("input_row_num", task.input_row_num, allocator);
203
0
        profile.AddMember("input_data_size", task.input_data_size, allocator);
204
0
        profile.AddMember("input_index_size", task.input_index_size, allocator);
205
0
        profile.AddMember("input_total_size", task.input_total_size, allocator);
206
0
        profile.AddMember("input_segments_num", task.input_segments_num, allocator);
207
208
0
        {
209
0
            rapidjson::Value v;
210
0
            v.SetString(task.input_version_range.c_str(),
211
0
                        static_cast<rapidjson::SizeType>(task.input_version_range.size()),
212
0
                        allocator);
213
0
            profile.AddMember("input_version_range", v, allocator);
214
0
        }
215
216
        // Output statistics
217
0
        profile.AddMember("merged_rows", task.merged_rows, allocator);
218
0
        profile.AddMember("filtered_rows", task.filtered_rows, allocator);
219
0
        profile.AddMember("output_rows", task.output_rows, allocator);
220
0
        profile.AddMember("output_row_num", task.output_row_num, allocator);
221
0
        profile.AddMember("output_data_size", task.output_data_size, allocator);
222
0
        profile.AddMember("output_index_size", task.output_index_size, allocator);
223
0
        profile.AddMember("output_total_size", task.output_total_size, allocator);
224
0
        profile.AddMember("output_segments_num", task.output_segments_num, allocator);
225
226
0
        {
227
0
            rapidjson::Value v;
228
0
            v.SetString(task.output_version.c_str(),
229
0
                        static_cast<rapidjson::SizeType>(task.output_version.size()), allocator);
230
0
            profile.AddMember("output_version", v, allocator);
231
0
        }
232
233
        // Merge performance
234
0
        profile.AddMember("merge_latency_ms", task.merge_latency_ms, allocator);
235
236
        // IO statistics
237
0
        profile.AddMember("bytes_read_from_local", task.bytes_read_from_local, allocator);
238
0
        profile.AddMember("bytes_read_from_remote", task.bytes_read_from_remote, allocator);
239
240
        // Resources
241
0
        profile.AddMember("peak_memory_bytes", task.peak_memory_bytes, allocator);
242
0
        profile.AddMember("is_vertical", task.is_vertical, allocator);
243
0
        profile.AddMember("permits", task.permits, allocator);
244
245
        // Vertical compaction progress
246
0
        profile.AddMember("vertical_total_groups", task.vertical_total_groups, allocator);
247
0
        profile.AddMember("vertical_completed_groups", task.vertical_completed_groups, allocator);
248
249
        // Status message (only for failed tasks)
250
0
        if (!task.status_msg.empty()) {
251
0
            rapidjson::Value v;
252
0
            v.SetString(task.status_msg.c_str(),
253
0
                        static_cast<rapidjson::SizeType>(task.status_msg.size()), allocator);
254
0
            profile.AddMember("status_msg", v, allocator);
255
0
        }
256
257
0
        profiles.PushBack(profile, allocator);
258
0
    }
259
260
0
    root.AddMember("compaction_profiles", profiles, allocator);
261
262
0
    rapidjson::StringBuffer str_buf;
263
0
    rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(str_buf);
264
0
    root.Accept(writer);
265
266
0
    HttpChannel::send_reply(req, HttpStatus::OK, str_buf.GetString());
267
0
}
268
269
#include "common/compile_check_end.h"
270
} // namespace doris