1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 #include "crimson/common/log.h"
5 #include "crimson/os/seastore/logging.h"
7 #include "crimson/os/seastore/segment_cleaner.h"
8 #include "crimson/os/seastore/transaction_manager.h"
11 seastar::logger
& logger() {
12 return crimson::get_logger(ceph_subsys_seastore_cleaner
);
16 SET_SUBSYS(seastore_cleaner
);
18 namespace crimson::os::seastore
{
20 void segment_info_set_t::segment_info_t::set_open() {
21 assert(state
== Segment::segment_state_t::EMPTY
);
22 state
= Segment::segment_state_t::OPEN
;
25 void segment_info_set_t::segment_info_t::set_empty() {
26 assert(state
== Segment::segment_state_t::CLOSED
);
27 state
= Segment::segment_state_t::EMPTY
;
30 void segment_info_set_t::segment_info_t::set_closed() {
31 state
= Segment::segment_state_t::CLOSED
;
34 bool SpaceTrackerSimple::equals(const SpaceTrackerI
&_other
) const
36 const auto &other
= static_cast<const SpaceTrackerSimple
&>(_other
);
38 if (other
.live_bytes_by_segment
.size() != live_bytes_by_segment
.size()) {
39 logger().error("{}: different segment counts, bug in test");
40 assert(0 == "segment counts should match");
44 bool all_match
= true;
45 for (auto i
= live_bytes_by_segment
.begin(), j
= other
.live_bytes_by_segment
.begin();
46 i
!= live_bytes_by_segment
.end(); ++i
, ++j
) {
47 if (i
->second
!= j
->second
) {
50 "{}: segment_id {} live bytes mismatch *this: {}, other: {}",
60 int64_t SpaceTrackerDetailed::SegmentMap::allocate(
61 device_segment_id_t segment
,
64 const extent_len_t block_size
)
66 assert(offset
% block_size
== 0);
67 assert(len
% block_size
== 0);
69 const auto b
= (offset
/ block_size
);
70 const auto e
= (offset
+ len
) / block_size
;
73 for (auto i
= b
; i
< e
; ++i
) {
77 "SegmentMap::allocate found allocated in {}, {} ~ {}",
84 "SegmentMap::allocate block {} allocated",
89 return update_usage(len
);
92 int64_t SpaceTrackerDetailed::SegmentMap::release(
93 device_segment_id_t segment
,
96 const extent_len_t block_size
)
98 assert(offset
% block_size
== 0);
99 assert(len
% block_size
== 0);
101 const auto b
= (offset
/ block_size
);
102 const auto e
= (offset
+ len
) / block_size
;
105 for (auto i
= b
; i
< e
; ++i
) {
109 "SegmentMap::release found unallocated in {}, {} ~ {}",
116 "SegmentMap::release block {} unallocated",
121 return update_usage(-(int64_t)len
);
124 bool SpaceTrackerDetailed::equals(const SpaceTrackerI
&_other
) const
126 const auto &other
= static_cast<const SpaceTrackerDetailed
&>(_other
);
128 if (other
.segment_usage
.size() != segment_usage
.size()) {
129 logger().error("{}: different segment counts, bug in test");
130 assert(0 == "segment counts should match");
134 bool all_match
= true;
135 for (auto i
= segment_usage
.begin(), j
= other
.segment_usage
.begin();
136 i
!= segment_usage
.end(); ++i
, ++j
) {
137 if (i
->second
.get_usage() != j
->second
.get_usage()) {
140 "{}: segment_id {} live bytes mismatch *this: {}, other: {}",
143 i
->second
.get_usage(),
144 j
->second
.get_usage());
150 void SpaceTrackerDetailed::SegmentMap::dump_usage(extent_len_t block_size
) const
152 for (unsigned i
= 0; i
< bitmap
.size(); ++i
) {
154 logger().debug(" {} still live", i
* block_size
);
159 void SpaceTrackerDetailed::dump_usage(segment_id_t id
) const
161 logger().debug("SpaceTrackerDetailed::dump_usage {}", id
);
162 segment_usage
[id
].dump_usage(
163 block_size_by_segment_manager
[id
.device_id()]);
166 SegmentCleaner::SegmentCleaner(
168 ExtentReaderRef
&& scr
,
170 : detailed(detailed
),
172 scanner(std::move(scr
)),
178 void SegmentCleaner::register_metrics()
180 namespace sm
= seastar::metrics
;
181 metrics
.add_group("segment_cleaner", {
182 sm::make_counter("segments_released", stats
.segments_released
,
183 sm::description("total number of extents released by SegmentCleaner")),
184 sm::make_counter("accumulated_blocked_ios", stats
.accumulated_blocked_ios
,
185 sm::description("accumulated total number of ios that were blocked by gc")),
186 sm::make_derive("empty_segments", stats
.empty_segments
,
187 sm::description("current empty segments")),
188 sm::make_derive("ios_blocking", stats
.ios_blocking
,
189 sm::description("IOs that are blocking on space usage")),
190 sm::make_derive("used_bytes", stats
.used_bytes
,
191 sm::description("the size of the space occupied by live extents")),
192 sm::make_derive("projected_used_bytes", stats
.projected_used_bytes
,
193 sm::description("the size of the space going to be occupied by new extents")),
194 sm::make_derive("avail_bytes",
196 return segments
.get_available_bytes();
198 sm::description("the size of the space not occupied")),
199 sm::make_derive("opened_segments",
201 return segments
.get_opened_segments();
203 sm::description("the number of segments whose state is open"))
207 SegmentCleaner::get_segment_ret
SegmentCleaner::get_segment(device_id_t id
)
209 for (auto it
= segments
.device_begin(id
);
210 it
!= segments
.device_end(id
);
213 auto& segment_info
= it
->second
;
214 if (segment_info
.is_empty()) {
216 logger().debug("{}: returning segment {}", __func__
, id
);
217 return get_segment_ret(
218 get_segment_ertr::ready_future_marker
{},
222 assert(0 == "out of space handling todo");
223 return get_segment_ret(
224 get_segment_ertr::ready_future_marker
{},
228 void SegmentCleaner::update_journal_tail_target(journal_seq_t target
)
231 "{}: {}, current tail target {}",
234 journal_tail_target
);
235 assert(journal_tail_target
== journal_seq_t() || target
>= journal_tail_target
);
236 if (journal_tail_target
== journal_seq_t() || target
> journal_tail_target
) {
237 journal_tail_target
= target
;
239 gc_process
.maybe_wake_on_space_used();
240 maybe_wake_gc_blocked_io();
243 void SegmentCleaner::update_journal_tail_committed(journal_seq_t committed
)
245 if (journal_tail_committed
== journal_seq_t() ||
246 committed
> journal_tail_committed
) {
248 "{}: update journal_tail_committed {}",
251 journal_tail_committed
= committed
;
253 if (journal_tail_target
== journal_seq_t() ||
254 committed
> journal_tail_target
) {
256 "{}: update journal_tail_target {}",
259 journal_tail_target
= committed
;
263 void SegmentCleaner::close_segment(segment_id_t segment
)
265 mark_closed(segment
);
268 SegmentCleaner::rewrite_dirty_ret
SegmentCleaner::rewrite_dirty(
272 LOG_PREFIX(SegmentCleaner::rewrite_dirty
);
273 return ecb
->get_next_dirty_extents(
276 config
.journal_rewrite_per_cycle
277 ).si_then([=, &t
](auto dirty_list
) {
278 return seastar::do_with(
279 std::move(dirty_list
),
280 [FNAME
, this, &t
](auto &dirty_list
) {
281 return trans_intr::do_for_each(
283 [FNAME
, this, &t
](auto &e
) {
284 DEBUGT("cleaning {}", t
, *e
);
285 return ecb
->rewrite_extent(t
, e
);
291 SegmentCleaner::gc_cycle_ret
SegmentCleaner::GCProcess::run()
293 return seastar::do_until(
294 [this] { return stopping
; },
296 return maybe_wait_should_run(
298 cleaner
.log_gc_state("GCProcess::run");
301 return seastar::now();
303 return cleaner
.do_gc_cycle();
309 SegmentCleaner::gc_cycle_ret
SegmentCleaner::do_gc_cycle()
311 if (gc_should_trim_journal()) {
312 return gc_trim_journal(
314 crimson::ct_error::assert_all
{
315 "GCProcess::run encountered invalid error in gc_trim_journal"
318 } else if (gc_should_reclaim_space()) {
319 return gc_reclaim_space(
321 crimson::ct_error::assert_all
{
322 "GCProcess::run encountered invalid error in gc_reclaim_space"
326 return seastar::now();
330 SegmentCleaner::gc_trim_journal_ret
SegmentCleaner::gc_trim_journal()
332 return repeat_eagain([this] {
333 return ecb
->with_transaction_intr(
334 Transaction::src_t::CLEANER_TRIM
,
338 return rewrite_dirty(t
, get_dirty_tail()
339 ).si_then([this, &t
] {
340 return ecb
->submit_transaction_direct(t
);
346 SegmentCleaner::gc_reclaim_space_ret
SegmentCleaner::gc_reclaim_space()
349 journal_seq_t next
= get_next_gc_target();
350 if (next
== journal_seq_t()) {
352 "SegmentCleaner::do_gc: no segments to gc");
353 return seastar::now();
356 std::make_unique
<ExtentReader::scan_extents_cursor
>(
359 "SegmentCleaner::do_gc: starting gc on segment {}",
362 ceph_assert(!scan_cursor
->is_complete());
365 return scanner
->scan_extents(
367 config
.reclaim_bytes_stride
368 ).safe_then([this](auto &&_extents
) {
369 return seastar::do_with(
371 [this](auto &extents
) {
372 return repeat_eagain([this, &extents
]() mutable {
374 "SegmentCleaner::gc_reclaim_space: processing {} extents",
376 return ecb
->with_transaction_intr(
377 Transaction::src_t::CLEANER_RECLAIM
,
379 [this, &extents
](auto& t
)
381 return trans_intr::do_for_each(
383 [this, &t
](auto &extent
) {
384 auto &[addr
, info
] = extent
;
386 "SegmentCleaner::gc_reclaim_space: checking extent {}",
388 return ecb
->get_extent_if_live(
394 ).si_then([addr
=addr
, &t
, this](CachedExtentRef ext
) {
397 "SegmentCleaner::gc_reclaim_space: addr {} dead, skipping",
399 return ExtentCallbackInterface::rewrite_extent_iertr::now();
402 "SegmentCleaner::gc_reclaim_space: addr {} alive, gc'ing {}",
405 return ecb
->rewrite_extent(
410 }).si_then([this, &t
] {
411 if (scan_cursor
->is_complete()) {
412 t
.mark_segment_to_release(scan_cursor
->get_segment_id());
414 return ecb
->submit_transaction_direct(t
);
419 }).safe_then([this] {
420 if (scan_cursor
->is_complete()) {
426 SegmentCleaner::init_segments_ret
SegmentCleaner::init_segments() {
427 logger().debug("SegmentCleaner::init_segments: {} segments", segments
.size());
428 return seastar::do_with(
429 std::vector
<std::pair
<segment_id_t
, segment_header_t
>>(),
430 [this](auto& segment_set
) {
431 return crimson::do_for_each(
434 [this, &segment_set
](auto& it
) {
435 auto segment_id
= it
.first
;
436 return scanner
->read_segment_header(
438 ).safe_then([&segment_set
, segment_id
, this](auto header
) {
439 if (header
.out_of_line
) {
441 "ExtentReader::init_segments: out-of-line segment {}",
443 init_mark_segment_closed(
445 header
.journal_segment_seq
,
449 "ExtentReader::init_segments: journal segment {}",
451 segment_set
.emplace_back(std::make_pair(segment_id
, std::move(header
)));
453 return seastar::now();
455 crimson::ct_error::enoent::handle([](auto) {
456 return init_segments_ertr::now();
458 crimson::ct_error::enodata::handle([](auto) {
459 return init_segments_ertr::now();
461 crimson::ct_error::input_output_error::pass_further
{}
463 }).safe_then([&segment_set
] {
464 return seastar::make_ready_future
<
465 std::vector
<std::pair
<segment_id_t
, segment_header_t
>>>(
466 std::move(segment_set
));