Coverage Report

Created: 2026-04-17 00:21

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