]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | ||
4 | #include "librbd/io/QosImageDispatch.h" | |
5 | #include "common/dout.h" | |
6 | #include "librbd/AsioEngine.h" | |
7 | #include "librbd/ImageCtx.h" | |
8 | #include "librbd/io/FlushTracker.h" | |
20effc67 | 9 | #include <utility> |
f67539c2 TL |
10 | |
11 | #define dout_subsys ceph_subsys_rbd | |
12 | #undef dout_prefix | |
13 | #define dout_prefix *_dout << "librbd::io::QosImageDispatch: " << this << " " \ | |
14 | << __func__ << ": " | |
15 | ||
16 | namespace librbd { | |
17 | namespace io { | |
18 | ||
19 | namespace { | |
20 | ||
21 | uint64_t get_extent_length(const Extents& extents) { | |
22 | uint64_t length = 0; | |
23 | for (auto& extent : extents) { | |
24 | length += extent.second; | |
25 | } | |
26 | return length; | |
27 | } | |
28 | ||
29 | uint64_t calculate_tokens(bool read_op, uint64_t extent_length, uint64_t flag) { | |
30 | if (read_op && ((flag & IMAGE_DISPATCH_FLAG_QOS_WRITE_MASK) != 0)) { | |
31 | return 0; | |
32 | } else if (!read_op && ((flag & IMAGE_DISPATCH_FLAG_QOS_READ_MASK) != 0)) { | |
33 | return 0; | |
34 | } | |
35 | ||
36 | return (((flag & IMAGE_DISPATCH_FLAG_QOS_BPS_MASK) != 0) ? extent_length : 1); | |
37 | } | |
38 | ||
20effc67 | 39 | static const std::pair<uint64_t, const char*> throttle_flags[] = { |
f67539c2 TL |
40 | {IMAGE_DISPATCH_FLAG_QOS_IOPS_THROTTLE, "rbd_qos_iops_throttle" }, |
41 | {IMAGE_DISPATCH_FLAG_QOS_BPS_THROTTLE, "rbd_qos_bps_throttle" }, | |
42 | {IMAGE_DISPATCH_FLAG_QOS_READ_IOPS_THROTTLE, "rbd_qos_read_iops_throttle" }, | |
43 | {IMAGE_DISPATCH_FLAG_QOS_WRITE_IOPS_THROTTLE, "rbd_qos_write_iops_throttle" }, | |
44 | {IMAGE_DISPATCH_FLAG_QOS_READ_BPS_THROTTLE, "rbd_qos_read_bps_throttle" }, | |
45 | {IMAGE_DISPATCH_FLAG_QOS_WRITE_BPS_THROTTLE, "rbd_qos_write_bps_throttle" } | |
46 | }; | |
47 | ||
48 | } // anonymous namespace | |
49 | ||
50 | template <typename I> | |
51 | QosImageDispatch<I>::QosImageDispatch(I* image_ctx) | |
52 | : m_image_ctx(image_ctx), m_flush_tracker(new FlushTracker<I>(image_ctx)) { | |
53 | auto cct = m_image_ctx->cct; | |
54 | ldout(cct, 5) << "ictx=" << image_ctx << dendl; | |
55 | ||
56 | SafeTimer *timer; | |
57 | ceph::mutex *timer_lock; | |
58 | ImageCtx::get_timer_instance(cct, &timer, &timer_lock); | |
20effc67 TL |
59 | for (auto [flag, name] : throttle_flags) { |
60 | m_throttles.emplace_back( | |
61 | flag, | |
62 | new TokenBucketThrottle(cct, name, 0, 0, timer, timer_lock)); | |
f67539c2 TL |
63 | } |
64 | } | |
65 | ||
66 | template <typename I> | |
67 | QosImageDispatch<I>::~QosImageDispatch() { | |
68 | for (auto t : m_throttles) { | |
69 | delete t.second; | |
70 | } | |
f67539c2 TL |
71 | } |
72 | ||
73 | template <typename I> | |
74 | void QosImageDispatch<I>::shut_down(Context* on_finish) { | |
75 | m_flush_tracker->shut_down(); | |
76 | on_finish->complete(0); | |
77 | } | |
78 | ||
79 | template <typename I> | |
80 | void QosImageDispatch<I>::apply_qos_schedule_tick_min(uint64_t tick) { | |
81 | for (auto pair : m_throttles) { | |
82 | pair.second->set_schedule_tick_min(tick); | |
83 | } | |
84 | } | |
85 | ||
86 | template <typename I> | |
87 | void QosImageDispatch<I>::apply_qos_limit(uint64_t flag, uint64_t limit, | |
88 | uint64_t burst, uint64_t burst_seconds) { | |
89 | auto cct = m_image_ctx->cct; | |
90 | TokenBucketThrottle *throttle = nullptr; | |
91 | for (auto pair : m_throttles) { | |
92 | if (flag == pair.first) { | |
93 | throttle = pair.second; | |
94 | break; | |
95 | } | |
96 | } | |
97 | ceph_assert(throttle != nullptr); | |
98 | ||
99 | int r = throttle->set_limit(limit, burst, burst_seconds); | |
100 | if (r < 0) { | |
101 | lderr(cct) << throttle->get_name() << ": invalid qos parameter: " | |
102 | << "burst(" << burst << ") is less than " | |
103 | << "limit(" << limit << ")" << dendl; | |
104 | // if apply failed, we should at least make sure the limit works. | |
105 | throttle->set_limit(limit, 0, 1); | |
106 | } | |
107 | ||
108 | if (limit) { | |
109 | m_qos_enabled_flag |= flag; | |
110 | } else { | |
111 | m_qos_enabled_flag &= ~flag; | |
112 | } | |
113 | } | |
114 | ||
20effc67 TL |
115 | template <typename I> |
116 | void QosImageDispatch<I>::apply_qos_exclude_ops(uint64_t exclude_ops) { | |
117 | m_qos_exclude_ops = exclude_ops; | |
118 | } | |
119 | ||
f67539c2 TL |
120 | template <typename I> |
121 | bool QosImageDispatch<I>::read( | |
122 | AioCompletion* aio_comp, Extents &&image_extents, ReadResult &&read_result, | |
123 | IOContext io_context, int op_flags, int read_flags, | |
124 | const ZTracer::Trace &parent_trace, uint64_t tid, | |
125 | std::atomic<uint32_t>* image_dispatch_flags, | |
126 | DispatchResult* dispatch_result, Context** on_finish, | |
127 | Context* on_dispatched) { | |
128 | auto cct = m_image_ctx->cct; | |
129 | ldout(cct, 20) << "tid=" << tid << ", image_extents=" << image_extents | |
130 | << dendl; | |
131 | ||
20effc67 TL |
132 | if (m_qos_exclude_ops & RBD_IO_OPERATION_READ) { |
133 | return false; | |
134 | } | |
135 | ||
f67539c2 TL |
136 | if (needs_throttle(true, image_extents, tid, image_dispatch_flags, |
137 | dispatch_result, on_finish, on_dispatched)) { | |
138 | return true; | |
139 | } | |
140 | ||
141 | return false; | |
142 | } | |
143 | ||
144 | template <typename I> | |
145 | bool QosImageDispatch<I>::write( | |
146 | AioCompletion* aio_comp, Extents &&image_extents, bufferlist &&bl, | |
1e59de90 | 147 | int op_flags, const ZTracer::Trace &parent_trace, |
f67539c2 TL |
148 | uint64_t tid, std::atomic<uint32_t>* image_dispatch_flags, |
149 | DispatchResult* dispatch_result, Context** on_finish, | |
150 | Context* on_dispatched) { | |
151 | auto cct = m_image_ctx->cct; | |
152 | ldout(cct, 20) << "tid=" << tid << ", image_extents=" << image_extents | |
153 | << dendl; | |
154 | ||
20effc67 TL |
155 | if (m_qos_exclude_ops & RBD_IO_OPERATION_WRITE) { |
156 | return false; | |
157 | } | |
158 | ||
f67539c2 TL |
159 | if (needs_throttle(false, image_extents, tid, image_dispatch_flags, |
160 | dispatch_result, on_finish, on_dispatched)) { | |
161 | return true; | |
162 | } | |
163 | ||
164 | return false; | |
165 | } | |
166 | ||
167 | template <typename I> | |
168 | bool QosImageDispatch<I>::discard( | |
169 | AioCompletion* aio_comp, Extents &&image_extents, | |
1e59de90 TL |
170 | uint32_t discard_granularity_bytes, const ZTracer::Trace &parent_trace, |
171 | uint64_t tid, std::atomic<uint32_t>* image_dispatch_flags, | |
f67539c2 TL |
172 | DispatchResult* dispatch_result, Context** on_finish, |
173 | Context* on_dispatched) { | |
174 | auto cct = m_image_ctx->cct; | |
175 | ldout(cct, 20) << "tid=" << tid << ", image_extents=" << image_extents | |
176 | << dendl; | |
177 | ||
20effc67 TL |
178 | if (m_qos_exclude_ops & RBD_IO_OPERATION_DISCARD) { |
179 | return false; | |
180 | } | |
181 | ||
f67539c2 TL |
182 | if (needs_throttle(false, image_extents, tid, image_dispatch_flags, |
183 | dispatch_result, on_finish, on_dispatched)) { | |
184 | return true; | |
185 | } | |
186 | ||
187 | return false; | |
188 | } | |
189 | ||
190 | template <typename I> | |
191 | bool QosImageDispatch<I>::write_same( | |
192 | AioCompletion* aio_comp, Extents &&image_extents, bufferlist &&bl, | |
1e59de90 | 193 | int op_flags, const ZTracer::Trace &parent_trace, |
f67539c2 TL |
194 | uint64_t tid, std::atomic<uint32_t>* image_dispatch_flags, |
195 | DispatchResult* dispatch_result, Context** on_finish, | |
196 | Context* on_dispatched) { | |
197 | auto cct = m_image_ctx->cct; | |
198 | ldout(cct, 20) << "tid=" << tid << ", image_extents=" << image_extents | |
199 | << dendl; | |
200 | ||
20effc67 TL |
201 | if (m_qos_exclude_ops & RBD_IO_OPERATION_WRITE_SAME) { |
202 | return false; | |
203 | } | |
204 | ||
f67539c2 TL |
205 | if (needs_throttle(false, image_extents, tid, image_dispatch_flags, |
206 | dispatch_result, on_finish, on_dispatched)) { | |
207 | return true; | |
208 | } | |
209 | ||
210 | return false; | |
211 | } | |
212 | ||
213 | template <typename I> | |
214 | bool QosImageDispatch<I>::compare_and_write( | |
1e59de90 TL |
215 | AioCompletion* aio_comp, Extents &&image_extents, |
216 | bufferlist &&cmp_bl, bufferlist &&bl, uint64_t *mismatch_offset, | |
217 | int op_flags, const ZTracer::Trace &parent_trace, | |
218 | uint64_t tid, std::atomic<uint32_t>* image_dispatch_flags, | |
f67539c2 TL |
219 | DispatchResult* dispatch_result, Context** on_finish, |
220 | Context* on_dispatched) { | |
221 | auto cct = m_image_ctx->cct; | |
222 | ldout(cct, 20) << "tid=" << tid << ", image_extents=" << image_extents | |
223 | << dendl; | |
224 | ||
20effc67 TL |
225 | if (m_qos_exclude_ops & RBD_IO_OPERATION_COMPARE_AND_WRITE) { |
226 | return false; | |
227 | } | |
228 | ||
f67539c2 TL |
229 | if (needs_throttle(false, image_extents, tid, image_dispatch_flags, |
230 | dispatch_result, on_finish, on_dispatched)) { | |
231 | return true; | |
232 | } | |
233 | ||
234 | return false; | |
235 | } | |
236 | ||
237 | template <typename I> | |
238 | bool QosImageDispatch<I>::flush( | |
239 | AioCompletion* aio_comp, FlushSource flush_source, | |
240 | const ZTracer::Trace &parent_trace, uint64_t tid, | |
241 | std::atomic<uint32_t>* image_dispatch_flags, | |
242 | DispatchResult* dispatch_result, Context** on_finish, | |
243 | Context* on_dispatched) { | |
244 | auto cct = m_image_ctx->cct; | |
245 | ldout(cct, 20) << "tid=" << tid << dendl; | |
246 | ||
247 | *dispatch_result = DISPATCH_RESULT_CONTINUE; | |
248 | m_flush_tracker->flush(on_dispatched); | |
249 | return true; | |
250 | } | |
251 | ||
252 | template <typename I> | |
253 | void QosImageDispatch<I>::handle_finished(int r, uint64_t tid) { | |
254 | auto cct = m_image_ctx->cct; | |
255 | ldout(cct, 20) << "tid=" << tid << dendl; | |
256 | ||
257 | m_flush_tracker->finish_io(tid); | |
258 | } | |
259 | ||
260 | template <typename I> | |
261 | bool QosImageDispatch<I>::set_throttle_flag( | |
262 | std::atomic<uint32_t>* image_dispatch_flags, uint32_t flag) { | |
263 | uint32_t expected = image_dispatch_flags->load(); | |
264 | uint32_t desired; | |
265 | do { | |
266 | desired = expected | flag; | |
267 | } while (!image_dispatch_flags->compare_exchange_weak(expected, desired)); | |
268 | ||
269 | return ((desired & IMAGE_DISPATCH_FLAG_QOS_MASK) == | |
270 | IMAGE_DISPATCH_FLAG_QOS_MASK); | |
271 | } | |
272 | ||
273 | template <typename I> | |
274 | bool QosImageDispatch<I>::needs_throttle( | |
275 | bool read_op, const Extents& image_extents, uint64_t tid, | |
276 | std::atomic<uint32_t>* image_dispatch_flags, | |
277 | DispatchResult* dispatch_result, Context** on_finish, | |
278 | Context* on_dispatched) { | |
279 | auto cct = m_image_ctx->cct; | |
280 | auto extent_length = get_extent_length(image_extents); | |
281 | bool all_qos_flags_set = false; | |
282 | ||
283 | if (!read_op) { | |
284 | m_flush_tracker->start_io(tid); | |
285 | *on_finish = new LambdaContext([this, tid, on_finish=*on_finish](int r) { | |
286 | handle_finished(r, tid); | |
287 | on_finish->complete(r); | |
288 | }); | |
289 | } | |
290 | *dispatch_result = DISPATCH_RESULT_CONTINUE; | |
291 | ||
292 | auto qos_enabled_flag = m_qos_enabled_flag; | |
293 | for (auto [flag, throttle] : m_throttles) { | |
294 | if ((qos_enabled_flag & flag) == 0) { | |
295 | all_qos_flags_set = set_throttle_flag(image_dispatch_flags, flag); | |
296 | continue; | |
297 | } | |
298 | ||
299 | auto tokens = calculate_tokens(read_op, extent_length, flag); | |
300 | if (tokens > 0 && | |
301 | throttle->get(tokens, this, &QosImageDispatch<I>::handle_throttle_ready, | |
302 | Tag{image_dispatch_flags, on_dispatched}, flag)) { | |
303 | ldout(cct, 15) << "on_dispatched=" << on_dispatched << ", " | |
304 | << "flag=" << flag << dendl; | |
305 | all_qos_flags_set = false; | |
306 | } else { | |
307 | all_qos_flags_set = set_throttle_flag(image_dispatch_flags, flag); | |
308 | } | |
309 | } | |
310 | return !all_qos_flags_set; | |
311 | } | |
312 | ||
313 | template <typename I> | |
314 | void QosImageDispatch<I>::handle_throttle_ready(Tag&& tag, uint64_t flag) { | |
315 | auto cct = m_image_ctx->cct; | |
316 | ldout(cct, 15) << "on_dispatched=" << tag.on_dispatched << ", " | |
317 | << "flag=" << flag << dendl; | |
318 | ||
319 | if (set_throttle_flag(tag.image_dispatch_flags, flag)) { | |
320 | // timer_lock is held -- so dispatch from outside the timer thread | |
321 | m_image_ctx->asio_engine->post(tag.on_dispatched, 0); | |
322 | } | |
323 | } | |
324 | ||
325 | } // namespace io | |
326 | } // namespace librbd | |
327 | ||
328 | template class librbd::io::QosImageDispatch<librbd::ImageCtx>; |