From 3c86c043727ea19642d5e61ccd99a619c4cb59d9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rados=C5=82aw=20Zarzy=C5=84ski?= Date: Fri, 1 Apr 2022 18:40:42 +0200 Subject: [PATCH] crimson/common: add basic infrastructure for op tracking MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Radosław Zarzyński --- src/crimson/common/operation.h | 130 ++++++++++++++++++++++++++++++++ src/crimson/osd/osd_operation.h | 29 +++++++ 2 files changed, 159 insertions(+) diff --git a/src/crimson/common/operation.h b/src/crimson/common/operation.h index d6de22393f3..296c9e85902 100644 --- a/src/crimson/common/operation.h +++ b/src/crimson/common/operation.h @@ -17,6 +17,8 @@ #include #include "include/ceph_assert.h" +#include "include/utime.h" +#include "common/Clock.h" #include "crimson/common/interruptible_future.h" namespace ceph { @@ -155,10 +157,138 @@ private: virtual const char *get_type_name() const = 0; }; +template +struct Event { + T* that() { + return static_cast(this); + } + const T* that() const { + return static_cast(this); + } + + template + void trigger(OpT&& op, Args&&... args) { + that()->internal_backend.handle(*that(), + std::forward(op), + std::forward(args)...); + } +}; + + +// simplest event type for recording things like beginning or end +// of TrackableOperation's life. +template +struct TimeEvent : Event { + struct Backend { + // `T` is passed solely to let implementations to discriminate + // basing on the type-of-event. + virtual void handle(T&, const Operation&) = 0; + }; + + // for the sake of dumping ops-in-flight. + struct InternalBackend final : Backend { + void handle(T&, const Operation&) override { + timestamp = ceph_clock_now(); + } + private: + utime_t timestamp; + } internal_backend; +}; + + template class BlockerT : public Blocker { public: + struct BlockingEvent : Event { + using Blocker = std::decay_t; + + struct Backend { + // `T` is based solely to let implementations to discriminate + // basing on the type-of-event. + virtual void handle(typename T::BlockingEvent&, const Operation&, const T&) = 0; + }; + + struct InternalBackend : Backend { + void handle(typename T::BlockingEvent&, + const Operation&, + const T& blocker) override { + this->timestamp = ceph_clock_now(); + this->blocker = &blocker; + } + + utime_t timestamp; + const T* blocker; + } internal_backend; + + // we don't want to make any BlockerT to be aware and coupled with + // an operation. to not templatize an entire path from an op to + // a blocker, type erasuring is used. + struct TriggerI { + TriggerI(BlockingEvent& event) : event(event) {} + + template + auto maybe_record_blocking(FutureT&& fut, const T& blocker) { + if (!fut.available()) { + // a full blown call via vtable. that's the cost for templatization + // avoidance. anyway, most of the things actually have the type + // knowledge. + record_blocking(blocker); + return std::forward(fut).finally( + [&event=this->event, &blocker] () mutable { + // beware trigger instance may be already dead when this + // is executed! + record_unblocking(event, blocker); + }); + } + return std::forward(fut); + } + virtual ~TriggerI() = default; + protected: + // it's for the sake of erasing the OpT type + virtual void record_blocking(const T& blocker) = 0; + + static void record_unblocking(BlockingEvent& event, const T& blocker) { + assert(event.internal_backend.blocker == &blocker); + event.internal_backend.blocker = nullptr; + } + + BlockingEvent& event; + }; + + template + struct Trigger : TriggerI { + Trigger(BlockingEvent& event, const OpT& op) : TriggerI(event), op(op) {} + + template + auto maybe_record_blocking(FutureT&& fut, const T& blocker) { + if (!fut.available()) { + // no need for the dynamic dispatch! if we're lucky, a compiler + // should collapse all these abstractions into a bunch of movs. + this->Trigger::record_blocking(blocker); + return std::forward(fut).finally( + [&event=this->event, &blocker] () mutable { + Trigger::record_unblocking(event, blocker); + }); + } + return std::forward(fut); + } + + protected: + void record_blocking(const T& blocker) override { + this->event.trigger(op, blocker); + } + + const OpT& op; + }; + }; + virtual ~BlockerT() = default; + template + decltype(auto) track_blocking(TriggerT&& trigger, Args&&... args) { + return std::forward(trigger).maybe_record_blocking( + std::forward(args)..., static_cast(*this)); + } + private: const char *get_type_name() const final { return static_cast(this)->type_name; diff --git a/src/crimson/osd/osd_operation.h b/src/crimson/osd/osd_operation.h index 60039850ef7..be507dd42fb 100644 --- a/src/crimson/osd/osd_operation.h +++ b/src/crimson/osd/osd_operation.h @@ -70,6 +70,35 @@ private: virtual void dump_detail(ceph::Formatter *f) const = 0; }; +template +class TrackableOperationT : public OperationT { + T* that() { + return static_cast(this); + } + const T* that() const { + return static_cast(this); + } + + template + decltype(auto) get_event() { + // all out derivates are supposed to define the list of tracking + // events accessible via `std::get`. This will usually boil down + // into an instance of `std::tuple`. + return std::get(that()->tracking_events); + } + +protected: + using OperationT::OperationT; + + template + void track_event(Args&&... args) { + // the idea is to have a visitor-like interface that allows to double + // dispatch (backend, blocker type) + get_event().trigger(*that(), std::forward(args)...); + } +}; + + /** * Maintains a set of lists of all active ops. */ -- 2.39.5