From: Mohit Agrawal Date: Tue, 27 May 2025 06:23:06 +0000 (+0530) Subject: mclock : Refactor the mClock scheduler source code X-Git-Tag: v21.0.0~256^2~412^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=2334345baabef7eea12f1faaf67e84429abb2a77;p=ceph.git mclock : Refactor the mClock scheduler source code Refactor the mClock scheduler source code into a common module so that both Classic and Crimson OSDs can use it. Fixes: https://tracker.ceph.com/issues/71445 Signed-off-by: Mohit Agrawal --- diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index ae34d330813f..53e225b47ba0 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -105,8 +105,10 @@ set(common_srcs utf8.c util.cc version.cc + mclock_common.cc tcp_info.cc) +include_directories(${CMAKE_SOURCE_DIR}/src/dmclock/support/src) if(WITH_SYSTEMD) list(APPEND common_srcs Journald.cc) diff --git a/src/common/mclock_common.cc b/src/common/mclock_common.cc new file mode 100644 index 000000000000..f560813939eb --- /dev/null +++ b/src/common/mclock_common.cc @@ -0,0 +1,517 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2025 IBM, Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include +#include + +#include "mclock_common.h" +#include "debug.h" + +#ifdef WITH_CRIMSON +#include "crimson/common/perf_counters_collection.h" +#else +#include "perf_counters_collection.h" +#endif + +#define dout_context cct +#define dout_subsys ceph_subsys_mclock +#undef dout_prefix +#define dout_prefix *_dout << "mclock_common: " + +namespace dmc = crimson::dmclock; + +std::ostream &operator<<(std::ostream &lhs, const SchedulerClass &c) +{ + lhs << static_cast(c); + switch (c) { + case SchedulerClass::background_best_effort: + return lhs << "background_best_effort"; + case SchedulerClass::background_recovery: + return lhs << "background_recovery"; + case SchedulerClass::client: + return lhs << "client"; +#ifdef WITH_CRIMSON + case SchedulerClass::repop: + return lhs << "repop"; +#endif + case SchedulerClass::immediate: + return lhs << "immediate"; + default: + return lhs; + } +} + +std::ostream& operator<<(std::ostream& out, + const client_profile_id_t& client_profile) { + out << " client_id: " << client_profile.client_id + << " profile_id: " << client_profile.profile_id; + return out; +} + +std::ostream& operator<<(std::ostream& out, + const scheduler_id_t& sched_id) { + out << "{ class_id: " << sched_id.class_id + << sched_id.client_profile_id; + return out << " }"; +} + +/* ClientRegistry holds the dmclock::ClientInfo configuration parameters + * (reservation (bytes/second), weight (unitless), limit (bytes/second)) + * for each IO class in the OSD (client, background_recovery, + * background_best_effort). + * + * mclock expects limit and reservation to have units of /second + * (bytes/second), but osd_mclock_scheduler_client_(lim|res) are provided + * as ratios of the OSD's capacity. We convert from the one to the other + * using the capacity_per_shard parameter. + * + * Note, mclock profile information will already have been set as a default + * for the osd_mclock_scheduler_client_* parameters prior to calling + * update_from_config -- see set_config_defaults_from_profile(). + */ +void ClientRegistry::update_from_config(const ConfigProxy &conf, + const double capacity_per_shard) +{ + auto get_res = [&](double res) { + if (res) { + return res * capacity_per_shard; + } else { + return default_min; // min reservation + } + }; + + auto get_lim = [&](double lim) { + if (lim) { + return lim * capacity_per_shard; + } else { + return default_max; // high limit + } + }; + + // Set external client infos + double res = conf.get_val( + "osd_mclock_scheduler_client_res"); + double lim = conf.get_val( + "osd_mclock_scheduler_client_lim"); + uint64_t wgt = conf.get_val( + "osd_mclock_scheduler_client_wgt"); + default_external_client_info.update( + get_res(res), + wgt, + get_lim(lim)); + + // Set background recovery client infos + res = conf.get_val( + "osd_mclock_scheduler_background_recovery_res"); + lim = conf.get_val( + "osd_mclock_scheduler_background_recovery_lim"); + wgt = conf.get_val( + "osd_mclock_scheduler_background_recovery_wgt"); + internal_client_infos[ + static_cast(SchedulerClass::background_recovery)].update( + get_res(res), + wgt, + get_lim(lim)); + + // Set background best effort client infos + res = conf.get_val( + "osd_mclock_scheduler_background_best_effort_res"); + lim = conf.get_val( + "osd_mclock_scheduler_background_best_effort_lim"); + wgt = conf.get_val( + "osd_mclock_scheduler_background_best_effort_wgt"); + internal_client_infos[ + static_cast(SchedulerClass::background_best_effort)].update( + get_res(res), + wgt, + get_lim(lim)); +} + +const dmc::ClientInfo *ClientRegistry::get_external_client( + const client_profile_id_t &client) const +{ + auto ret = external_client_infos.find(client); + if (ret == external_client_infos.end()) + return &default_external_client_info; + else + return &(ret->second); +} + +const dmc::ClientInfo *ClientRegistry::get_info( + const scheduler_id_t &id) const { + switch (id.class_id) { + case SchedulerClass::immediate: + ceph_assert(0 == "Cannot schedule immediate"); + return (dmc::ClientInfo*)nullptr; + case SchedulerClass::client: + return get_external_client(id.client_profile_id); + default: + ceph_assert(static_cast(id.class_id) < internal_client_infos.size()); + return &internal_client_infos[static_cast(id.class_id)]; + } +} + +static std::ostream &operator<<( + std::ostream &lhs, const profile_t::client_config_t &rhs) +{ + return lhs << "{res: " << rhs.reservation + << ", wgt: " << rhs.weight + << ", lim: " << rhs.limit + << "}"; +} + + +static std::ostream &operator<<(std::ostream &lhs, const profile_t &rhs) +{ + return lhs << "[client: " << rhs.client + << ", background_recovery: " << rhs.background_recovery + << ", background_best_effort: " << rhs.background_best_effort + << "]"; +} + +void MclockConfig::set_config_defaults_from_profile() +{ + // Let only a single osd shard (id:0) set the profile configs + if (shard_id > 0) { + return; + } + + /** + * high_client_ops + * + * Client Allocation: + * reservation: 60% | weight: 2 | limit: 0 (max) | + * Background Recovery Allocation: + * reservation: 40% | weight: 1 | limit: 0 (max) | + * Background Best Effort Allocation: + * reservation: 0 (min) | weight: 1 | limit: 70% | + */ + static constexpr profile_t high_client_ops_profile{ + { .6, 2, 0 }, + { .4, 1, 0 }, + { 0, 1, .7 } + }; + + /** + * high_recovery_ops + * + * Client Allocation: + * reservation: 30% | weight: 1 | limit: 0 (max) | + * Background Recovery Allocation: + * reservation: 70% | weight: 2 | limit: 0 (max) | + * Background Best Effort Allocation: + * reservation: 0 (min) | weight: 1 | limit: 0 (max) | + */ + static constexpr profile_t high_recovery_ops_profile{ + { .3, 1, 0 }, + { .7, 2, 0 }, + { 0, 1, 0 } + }; + + /** + * balanced + * + * Client Allocation: + * reservation: 50% | weight: 1 | limit: 0 (max) | + * Background Recovery Allocation: + * reservation: 50% | weight: 1 | limit: 0 (max) | + * Background Best Effort Allocation: + * reservation: 0 (min) | weight: 1 | limit: 90% | + */ + static constexpr profile_t balanced_profile{ + { .5, 1, 0 }, + { .5, 1, 0 }, + { 0, 1, .9 } + }; + + const profile_t *profile = nullptr; + auto mclock_profile = cct->_conf.get_val("osd_mclock_profile"); + if (mclock_profile == "high_client_ops") { + profile = &high_client_ops_profile; + dout(10) << "Setting high_client_ops profile " << *profile << dendl; + } else if (mclock_profile == "high_recovery_ops") { + profile = &high_recovery_ops_profile; + dout(10) << "Setting high_recovery_ops profile " << *profile << dendl; + } else if (mclock_profile == "balanced") { + profile = &balanced_profile; + dout(10) << "Setting balanced profile " << *profile << dendl; + } else if (mclock_profile == "custom") { + dout(10) << "Profile set to custom, not setting defaults" << dendl; + return; + } else { + derr << "Invalid mclock profile: " << mclock_profile << dendl; + ceph_assert("Invalid choice of mclock profile" == 0); + return; + } + ceph_assert(nullptr != profile); + + auto set_config = [&conf = cct->_conf](const char *key, auto val) { + conf.set_val_default(key, std::to_string(val)); + }; + + set_config("osd_mclock_scheduler_client_res", profile->client.reservation); + set_config("osd_mclock_scheduler_client_wgt", profile->client.weight); + set_config("osd_mclock_scheduler_client_lim", profile->client.limit); + + set_config( + "osd_mclock_scheduler_background_recovery_res", + profile->background_recovery.reservation); + set_config( + "osd_mclock_scheduler_background_recovery_wgt", + profile->background_recovery.weight); + set_config( + "osd_mclock_scheduler_background_recovery_lim", + profile->background_recovery.limit); + + set_config( + "osd_mclock_scheduler_background_best_effort_res", + profile->background_best_effort.reservation); + set_config( + "osd_mclock_scheduler_background_best_effort_wgt", + profile->background_best_effort.weight); + set_config( + "osd_mclock_scheduler_background_best_effort_lim", + profile->background_best_effort.limit); + + cct->_conf.apply_changes(nullptr); +} + +void MclockConfig::set_osd_capacity_params_from_config() +{ + uint64_t osd_bandwidth_capacity; + double osd_iop_capacity; + + std::tie(osd_bandwidth_capacity, osd_iop_capacity) = [&] { + if (is_rotational) { + return std::make_tuple( + cct->_conf.get_val( + "osd_mclock_max_sequential_bandwidth_hdd"), + cct->_conf.get_val("osd_mclock_max_capacity_iops_hdd")); + } else { + return std::make_tuple( + cct->_conf.get_val( + "osd_mclock_max_sequential_bandwidth_ssd"), + cct->_conf.get_val("osd_mclock_max_capacity_iops_ssd")); + } + }(); + + osd_bandwidth_capacity = std::max(1, osd_bandwidth_capacity); + osd_iop_capacity = std::max(1.0, osd_iop_capacity); + + osd_bandwidth_cost_per_io = + static_cast(osd_bandwidth_capacity) / osd_iop_capacity; + osd_bandwidth_capacity_per_shard = + static_cast(osd_bandwidth_capacity) / + static_cast(num_shards); + dout(1) << __func__ << ": osd_bandwidth_cost_per_io: " + << std::fixed << std::setprecision(2) + << osd_bandwidth_cost_per_io << " bytes/io" + << ", osd_bandwidth_capacity_per_shard " + << osd_bandwidth_capacity_per_shard << " bytes/second" + << dendl; +} + +void MclockConfig::init_logger() +{ + PerfCountersBuilder m(cct, "mclock-shard-queue-" + std::to_string(shard_id), + l_mclock_first, l_mclock_last); + + m.add_u64_counter(l_mclock_immediate_queue_len, "mclock_immediate_queue_len", + "high_priority op count in mclock queue"); + m.add_u64_counter(l_mclock_client_queue_len, "mclock_client_queue_len", + "client type op count in mclock queue"); + m.add_u64_counter(l_mclock_recovery_queue_len, "mclock_recovery_queue_len", + "background_recovery type op count in mclock queue"); + m.add_u64_counter(l_mclock_best_effort_queue_len, "mclock_best_effort_queue_len", + "background_best_effort type op count in mclock queue"); + m.add_u64_counter(l_mclock_all_type_queue_len, "mclock_all_type_queue_len", + "all type op count in mclock queue"); + + logger = m.create_perf_counters(); + cct->get_perfcounters_collection()->add(logger); + + logger->set(l_mclock_immediate_queue_len, 0); + logger->set(l_mclock_client_queue_len, 0); + logger->set(l_mclock_recovery_queue_len, 0); + logger->set(l_mclock_best_effort_queue_len, 0); + logger->set(l_mclock_all_type_queue_len, 0); +} + +void MclockConfig::get_mclock_counter(scheduler_id_t id) +{ + if (!logger) { + return; + } + + /* op enter mclock queue will +1 */ + logger->inc(l_mclock_all_type_queue_len); + + switch (id.class_id) { + case SchedulerClass::immediate: + logger->inc(l_mclock_immediate_queue_len); + break; + case SchedulerClass::client: + logger->inc(l_mclock_client_queue_len); + break; + case SchedulerClass::background_recovery: + logger->inc(l_mclock_recovery_queue_len); + break; + case SchedulerClass::background_best_effort: + logger->inc(l_mclock_best_effort_queue_len); + break; + default: + derr << __func__ << " unknown class_id=" << id.class_id + << " unknown id=" << id << dendl; + break; + } +} + +void MclockConfig::put_mclock_counter(scheduler_id_t id) +{ + if (!logger) { + return; + } + + /* op leave mclock queue will -1 */ + logger->dec(l_mclock_all_type_queue_len); + + switch (id.class_id) { + case SchedulerClass::immediate: + logger->dec(l_mclock_immediate_queue_len); + break; + case SchedulerClass::client: + logger->dec(l_mclock_client_queue_len); + break; + case SchedulerClass::background_recovery: + logger->dec(l_mclock_recovery_queue_len); + break; + case SchedulerClass::background_best_effort: + logger->dec(l_mclock_best_effort_queue_len); + break; + default: + derr << __func__ << " unknown class_id=" << id.class_id + << " unknown id=" << id << dendl; + break; + } +} + +double MclockConfig::get_cost_per_io() const { + return osd_bandwidth_cost_per_io; +} + +double MclockConfig::get_capacity_per_shard() const { + return osd_bandwidth_capacity_per_shard; +} + +uint32_t MclockConfig::calc_scaled_cost(int item_cost) +{ + auto cost = static_cast( + std::max( + 1, // ensure cost is non-zero and positive + item_cost)); + auto cost_per_io = static_cast(osd_bandwidth_cost_per_io); + + return std::max(cost, cost_per_io); +} + +void MclockConfig::mclock_handle_conf_change(const ConfigProxy& conf, + const std::set + &changed) +{ + if (changed.count("osd_mclock_max_capacity_iops_hdd") || + changed.count("osd_mclock_max_capacity_iops_ssd")) { + set_osd_capacity_params_from_config(); + client_registry.update_from_config( + conf, osd_bandwidth_capacity_per_shard); + } + if (changed.count("osd_mclock_max_sequential_bandwidth_hdd") || + changed.count("osd_mclock_max_sequential_bandwidth_ssd")) { + set_osd_capacity_params_from_config(); + client_registry.update_from_config( + conf, osd_bandwidth_capacity_per_shard); + } + if (changed.count("osd_mclock_profile")) { + set_config_defaults_from_profile(); + client_registry.update_from_config( + conf, osd_bandwidth_capacity_per_shard); + } + + auto get_changed_key = [&changed]() -> std::optional { + static const std::vector qos_params = { + "osd_mclock_scheduler_client_res", + "osd_mclock_scheduler_client_wgt", + "osd_mclock_scheduler_client_lim", + "osd_mclock_scheduler_background_recovery_res", + "osd_mclock_scheduler_background_recovery_wgt", + "osd_mclock_scheduler_background_recovery_lim", + "osd_mclock_scheduler_background_best_effort_res", + "osd_mclock_scheduler_background_best_effort_wgt", + "osd_mclock_scheduler_background_best_effort_lim" + }; + + for (auto &qp : qos_params) { + if (changed.count(qp)) { + return qp; + } + } + return std::nullopt; + }; + if (auto key = get_changed_key(); key.has_value()) { + auto mclock_profile = cct->_conf.get_val("osd_mclock_profile"); + if (mclock_profile == "custom") { + client_registry.update_from_config( + conf, osd_bandwidth_capacity_per_shard); + } else { + // Attempt to change QoS parameter for a built-in profile. Restore the + // profile defaults by making one of the OSD shards remove the key from + // config monitor store. Note: monc is included in the check since the + // mock unit test currently doesn't initialize it. + if (shard_id == 0 && monc) { + static const std::vector osds = { + "osd", + "osd." + std::to_string(whoami) + }; + + for (auto osd : osds) { + std::string cmd = + "{" + "\"prefix\": \"config rm\", " + "\"who\": \"" + osd + "\", " + "\"name\": \"" + *key + "\"" + "}"; + std::vector vcmd{cmd}; + + dout(10) << __func__ << " Removing Key: " << *key + << " for " << osd << " from Mon db" << dendl; + monc->start_mon_command(vcmd, {}, nullptr, nullptr, nullptr); + } + } + } + // Alternatively, the QoS parameter, if set ephemerally for this OSD via + // the 'daemon' or 'tell' interfaces must be removed. + if (!cct->_conf.rm_val(*key)) { + dout(10) << __func__ << " Restored " << *key << " to default" << dendl; + cct->_conf.apply_changes(nullptr); + } + } +} + +MclockConfig::~MclockConfig() +{ + if (logger) { + cct->get_perfcounters_collection()->remove(logger); + delete logger; + logger = nullptr; + } +} diff --git a/src/common/mclock_common.h b/src/common/mclock_common.h new file mode 100644 index 000000000000..331f61bdfdd3 --- /dev/null +++ b/src/common/mclock_common.h @@ -0,0 +1,184 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2025 IBM, Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + + +#pragma once +#include "config.h" +#include "ceph_context.h" +#include "dmclock/src/dmclock_server.h" +#ifndef WITH_CRIMSON + #include "mon/MonClient.h" +#else + #include "crimson/mon/MonClient.h" +#endif + +// scheduler class for classic +enum class op_scheduler_class : uint8_t { + background_recovery = 0, + background_best_effort, + immediate, + client, +}; + +// scheduler class for Crimson +enum class scheduler_class_t : uint8_t { + background_recovery = 0, + background_best_effort, + client, + repop, + immediate, +}; + +#ifdef WITH_CRIMSON +using SchedulerClass = scheduler_class_t; +#else +using SchedulerClass = op_scheduler_class; +#endif + +enum { + l_mclock_first = 15000, + l_mclock_immediate_queue_len, + l_mclock_client_queue_len, + l_mclock_recovery_queue_len, + l_mclock_best_effort_queue_len, + l_mclock_all_type_queue_len, + l_mclock_last, +}; + +constexpr double default_min = 0.0; +constexpr double default_max = std::numeric_limits::is_iec559 ? + std::numeric_limits::infinity() : + std::numeric_limits::max(); + +std::ostream& operator<<(std::ostream& out, const SchedulerClass& class_id); + +/** + * profile_t + * + * mclock profile -- 3 params for each of 3 client classes + * 0 (min): specifies no minimum reservation + * 0 (max): specifies no upper limit + */ + +struct profile_t { + struct client_config_t { + double reservation; + uint64_t weight; + double limit; + }; + client_config_t client; + client_config_t background_recovery; + client_config_t background_best_effort; +}; + +struct client_profile_id_t { + uint64_t client_id = 0; + uint64_t profile_id = 0; + + client_profile_id_t(uint64_t _client_id, uint64_t _profile_id) : + client_id(_client_id), + profile_id(_profile_id) {} + + client_profile_id_t() = default; + + auto operator<=>(const client_profile_id_t&) const = default; + friend std::ostream& operator<<(std::ostream& out, + const client_profile_id_t& client_profile); +}; + +struct scheduler_id_t { + SchedulerClass class_id; + client_profile_id_t client_profile_id; + + auto operator<=>(const scheduler_id_t&) const = default; + friend std::ostream& operator<<(std::ostream& out, + const scheduler_id_t& sched_id); +}; + + +class ClientRegistry { + static constexpr size_t internal_client_count = + static_cast(SchedulerClass::background_best_effort) + 1; + std::vector internal_client_infos; + + crimson::dmclock::ClientInfo default_external_client_info = {1, 1, 1}; + std::map external_client_infos; + const crimson::dmclock::ClientInfo *get_external_client( + const client_profile_id_t &client) const; + public: + ClientRegistry() { + internal_client_infos.reserve(internal_client_count); + // Fill array with default ClientInfo instances + for (size_t i = 0; i < internal_client_count; ++i) { + internal_client_infos.emplace_back(1, 1, 1); + } + } + void update_from_config(const ConfigProxy &conf, + double capacity_per_shard); + const crimson::dmclock::ClientInfo *get_info( + const scheduler_id_t &id) const; +}; + +class MclockConfig { +private: + CephContext *cct; + uint32_t num_shards; + bool is_rotational; + PerfCounters *logger; + int shard_id; + int whoami; + double osd_bandwidth_cost_per_io; + double osd_bandwidth_capacity_per_shard; + ClientRegistry& client_registry; + #ifndef WITH_CRIMSON + MonClient *monc; + #endif +public: + #ifdef WITH_CRIMSON + MclockConfig(CephContext *cct, ClientRegistry& creg, + uint32_t num_shards, bool is_rotational, int shard_id, + int whoami):cct(cct), + num_shards(num_shards), + is_rotational(is_rotational), + logger(nullptr),shard_id(shard_id), + whoami(whoami), osd_bandwidth_cost_per_io(0.0), + osd_bandwidth_capacity_per_shard(0.0), + client_registry(creg) + {} + #else + MclockConfig(CephContext *cct, ClientRegistry& creg, + MonClient *monc, uint32_t num_shards, bool is_rotational, + int shard_id, int whoami):cct(cct), + num_shards(num_shards), + is_rotational(is_rotational), + logger(nullptr),shard_id(shard_id), + whoami(whoami), + osd_bandwidth_cost_per_io(0.0), + osd_bandwidth_capacity_per_shard(0.0), + client_registry(creg), monc(monc) + {} +#endif + ~MclockConfig(); + void set_config_defaults_from_profile(); + void set_osd_capacity_params_from_config(); + void init_logger(); + void get_mclock_counter(scheduler_id_t id); + void put_mclock_counter(scheduler_id_t id); + double get_cost_per_io() const; + double get_capacity_per_shard() const; + void mclock_handle_conf_change(const ConfigProxy& conf, + const std::set &changed); + uint32_t calc_scaled_cost(int item_cost); +}; diff --git a/src/osd/scheduler/OpSchedulerItem.cc b/src/osd/scheduler/OpSchedulerItem.cc index 9d680c1b95b7..1b11c147cbe0 100644 --- a/src/osd/scheduler/OpSchedulerItem.cc +++ b/src/osd/scheduler/OpSchedulerItem.cc @@ -19,11 +19,6 @@ namespace ceph::osd::scheduler { -std::ostream& operator<<(std::ostream& out, const op_scheduler_class& class_id) { - out << static_cast(class_id); - return out; -} - void PGOpItem::run( OSD *osd, OSDShard *sdata, diff --git a/src/osd/scheduler/OpSchedulerItem.h b/src/osd/scheduler/OpSchedulerItem.h index d0281cf84e7f..fb5249c8545d 100644 --- a/src/osd/scheduler/OpSchedulerItem.h +++ b/src/osd/scheduler/OpSchedulerItem.h @@ -23,6 +23,7 @@ #include "osd/PG.h" #include "osd/PGPeeringEvent.h" #include "messages/MOSDOp.h" +#include "common/mclock_common.h" class OSD; @@ -30,15 +31,6 @@ struct OSDShard; namespace ceph::osd::scheduler { -enum class op_scheduler_class : uint8_t { - background_recovery = 0, - background_best_effort, - immediate, - client, -}; - -std::ostream& operator<<(std::ostream& out, const op_scheduler_class& class_id); - class OpSchedulerItem { public: // Abstraction for operations queueable in the op queue @@ -76,7 +68,7 @@ public: virtual std::string print() const = 0; virtual void run(OSD *osd, OSDShard *sdata, PGRef& pg, ThreadPool::TPHandle &handle) = 0; - virtual op_scheduler_class get_scheduler_class() const = 0; + virtual SchedulerClass get_scheduler_class() const = 0; virtual ~OpQueueable() {} friend std::ostream& operator<<(std::ostream& out, const OpQueueable& q) { @@ -161,7 +153,7 @@ public: return qitem->peering_requires_pg(); } - op_scheduler_class get_scheduler_class() const { + SchedulerClass get_scheduler_class() const { return qitem->get_scheduler_class(); } @@ -201,13 +193,13 @@ protected: return pgid; } - static op_scheduler_class priority_to_scheduler_class(int priority) { + static SchedulerClass priority_to_scheduler_class(int priority) { if (priority >= CEPH_MSG_PRIO_HIGH) { - return op_scheduler_class::immediate; + return SchedulerClass::immediate; } else if (priority >= PeeringState::recovery_msg_priority_t::DEGRADED) { - return op_scheduler_class::background_recovery; + return SchedulerClass::background_recovery; } else { - return op_scheduler_class::background_best_effort; + return SchedulerClass::background_best_effort; } } @@ -240,13 +232,13 @@ public: return op; } - op_scheduler_class get_scheduler_class() const final { + SchedulerClass get_scheduler_class() const final { auto type = op->get_req()->get_type(); if (type == CEPH_MSG_OSD_OP || type == CEPH_MSG_OSD_BACKOFF) { - return op_scheduler_class::client; + return SchedulerClass::client; } else { - return op_scheduler_class::immediate; + return SchedulerClass::immediate; } } @@ -273,8 +265,8 @@ public: const PGCreateInfo *creates_pg() const override { return evt->create_info.get(); } - op_scheduler_class get_scheduler_class() const final { - return op_scheduler_class::immediate; + SchedulerClass get_scheduler_class() const final { + return SchedulerClass::immediate; } }; @@ -296,8 +288,8 @@ public: } void run( OSD *osd, OSDShard *sdata, PGRef& pg, ThreadPool::TPHandle &handle) final; - op_scheduler_class get_scheduler_class() const final { - return op_scheduler_class::background_best_effort; + SchedulerClass get_scheduler_class() const final { + return SchedulerClass::background_best_effort; } }; @@ -319,8 +311,8 @@ public: } void run( OSD *osd, OSDShard *sdata, PGRef& pg, ThreadPool::TPHandle &handle) final; - op_scheduler_class get_scheduler_class() const final { - return op_scheduler_class::background_best_effort; + SchedulerClass get_scheduler_class() const final { + return SchedulerClass::background_best_effort; } }; @@ -359,9 +351,9 @@ class PGScrubItem : public PGOpQueueable { OSDShard* sdata, PGRef& pg, ThreadPool::TPHandle& handle) override = 0; - op_scheduler_class get_scheduler_class() const final + SchedulerClass get_scheduler_class() const final { - return op_scheduler_class::background_best_effort; + return SchedulerClass::background_best_effort; } }; @@ -507,7 +499,7 @@ public: } void run( OSD *osd, OSDShard *sdata, PGRef& pg, ThreadPool::TPHandle &handle) final; - op_scheduler_class get_scheduler_class() const final { + SchedulerClass get_scheduler_class() const final { return priority_to_scheduler_class(priority); } }; @@ -535,7 +527,7 @@ public: } void run( OSD *osd, OSDShard *sdata, PGRef& pg, ThreadPool::TPHandle &handle) final; - op_scheduler_class get_scheduler_class() const final { + SchedulerClass get_scheduler_class() const final { return priority_to_scheduler_class(priority); } }; @@ -559,8 +551,8 @@ public: } void run( OSD *osd, OSDShard *sdata, PGRef& pg, ThreadPool::TPHandle &handle) final; - op_scheduler_class get_scheduler_class() const final { - return op_scheduler_class::background_best_effort; + SchedulerClass get_scheduler_class() const final { + return SchedulerClass::background_best_effort; } }; @@ -598,7 +590,7 @@ public: return op; } - op_scheduler_class get_scheduler_class() const final { + SchedulerClass get_scheduler_class() const final { return priority_to_scheduler_class(op->get_req()->get_priority()); } @@ -618,7 +610,7 @@ struct fmt::formatter { { // matching existing op_scheduler_item_t::operator<<() format using class_t = - std::underlying_type_t; + std::underlying_type_t; const auto qos_cost = opsi.was_queued_via_mclock() ? fmt::format(" qos_cost {}", opsi.qos_cost) : ""; diff --git a/src/osd/scheduler/mClockScheduler.cc b/src/osd/scheduler/mClockScheduler.cc index 225873a2f643..216e426fda1f 100644 --- a/src/osd/scheduler/mClockScheduler.cc +++ b/src/osd/scheduler/mClockScheduler.cc @@ -37,374 +37,9 @@ using namespace std::literals; namespace ceph::osd::scheduler { -void mClockScheduler::_get_mclock_counter(scheduler_id_t id) -{ - if (!logger) { - return; - } - - /* op enter mclock queue will +1 */ - logger->inc(l_mclock_all_type_queue_len); - - switch (id.class_id) { - case op_scheduler_class::immediate: - logger->inc(l_mclock_immediate_queue_len); - break; - case op_scheduler_class::client: - logger->inc(l_mclock_client_queue_len); - break; - case op_scheduler_class::background_recovery: - logger->inc(l_mclock_recovery_queue_len); - break; - case op_scheduler_class::background_best_effort: - logger->inc(l_mclock_best_effort_queue_len); - break; - default: - derr << __func__ << " unknown class_id=" << id.class_id - << " unknown id=" << id << dendl; - break; - } -} - -void mClockScheduler::_put_mclock_counter(scheduler_id_t id) -{ - if (!logger) { - return; - } - - /* op leave mclock queue will -1 */ - logger->dec(l_mclock_all_type_queue_len); - - switch (id.class_id) { - case op_scheduler_class::immediate: - logger->dec(l_mclock_immediate_queue_len); - break; - case op_scheduler_class::client: - logger->dec(l_mclock_client_queue_len); - break; - case op_scheduler_class::background_recovery: - logger->dec(l_mclock_recovery_queue_len); - break; - case op_scheduler_class::background_best_effort: - logger->dec(l_mclock_best_effort_queue_len); - break; - default: - derr << __func__ << " unknown class_id=" << id.class_id - << " unknown id=" << id << dendl; - break; - } -} - -void mClockScheduler::_init_logger() -{ - PerfCountersBuilder m(cct, "mclock-shard-queue-" + std::to_string(shard_id), - l_mclock_first, l_mclock_last); - - m.add_u64_counter(l_mclock_immediate_queue_len, "mclock_immediate_queue_len", - "high_priority op count in mclock queue"); - m.add_u64_counter(l_mclock_client_queue_len, "mclock_client_queue_len", - "client type op count in mclock queue"); - m.add_u64_counter(l_mclock_recovery_queue_len, "mclock_recovery_queue_len", - "background_recovery type op count in mclock queue"); - m.add_u64_counter(l_mclock_best_effort_queue_len, "mclock_best_effort_queue_len", - "background_best_effort type op count in mclock queue"); - m.add_u64_counter(l_mclock_all_type_queue_len, "mclock_all_type_queue_len", - "all type op count in mclock queue"); - - logger = m.create_perf_counters(); - cct->get_perfcounters_collection()->add(logger); - - logger->set(l_mclock_immediate_queue_len, 0); - logger->set(l_mclock_client_queue_len, 0); - logger->set(l_mclock_recovery_queue_len, 0); - logger->set(l_mclock_best_effort_queue_len, 0); - logger->set(l_mclock_all_type_queue_len, 0); -} - -/* ClientRegistry holds the dmclock::ClientInfo configuration parameters - * (reservation (bytes/second), weight (unitless), limit (bytes/second)) - * for each IO class in the OSD (client, background_recovery, - * background_best_effort). - * - * mclock expects limit and reservation to have units of /second - * (bytes/second), but osd_mclock_scheduler_client_(lim|res) are provided - * as ratios of the OSD's capacity. We convert from the one to the other - * using the capacity_per_shard parameter. - * - * Note, mclock profile information will already have been set as a default - * for the osd_mclock_scheduler_client_* parameters prior to calling - * update_from_config -- see set_config_defaults_from_profile(). - */ -void mClockScheduler::ClientRegistry::update_from_config( - const ConfigProxy &conf, - const double capacity_per_shard) -{ - - auto get_res = [&](double res) { - if (res) { - return res * capacity_per_shard; - } else { - return default_min; // min reservation - } - }; - - auto get_lim = [&](double lim) { - if (lim) { - return lim * capacity_per_shard; - } else { - return default_max; // high limit - } - }; - - // Set external client infos - double res = conf.get_val( - "osd_mclock_scheduler_client_res"); - double lim = conf.get_val( - "osd_mclock_scheduler_client_lim"); - uint64_t wgt = conf.get_val( - "osd_mclock_scheduler_client_wgt"); - default_external_client_info.update( - get_res(res), - wgt, - get_lim(lim)); - - // Set background recovery client infos - res = conf.get_val( - "osd_mclock_scheduler_background_recovery_res"); - lim = conf.get_val( - "osd_mclock_scheduler_background_recovery_lim"); - wgt = conf.get_val( - "osd_mclock_scheduler_background_recovery_wgt"); - internal_client_infos[ - static_cast(op_scheduler_class::background_recovery)].update( - get_res(res), - wgt, - get_lim(lim)); - - // Set background best effort client infos - res = conf.get_val( - "osd_mclock_scheduler_background_best_effort_res"); - lim = conf.get_val( - "osd_mclock_scheduler_background_best_effort_lim"); - wgt = conf.get_val( - "osd_mclock_scheduler_background_best_effort_wgt"); - internal_client_infos[ - static_cast(op_scheduler_class::background_best_effort)].update( - get_res(res), - wgt, - get_lim(lim)); -} - -const dmc::ClientInfo *mClockScheduler::ClientRegistry::get_external_client( - const client_profile_id_t &client) const -{ - auto ret = external_client_infos.find(client); - if (ret == external_client_infos.end()) - return &default_external_client_info; - else - return &(ret->second); -} - -const dmc::ClientInfo *mClockScheduler::ClientRegistry::get_info( - const scheduler_id_t &id) const { - switch (id.class_id) { - case op_scheduler_class::immediate: - ceph_assert(0 == "Cannot schedule immediate"); - return (dmc::ClientInfo*)nullptr; - case op_scheduler_class::client: - return get_external_client(id.client_profile_id); - default: - ceph_assert(static_cast(id.class_id) < internal_client_infos.size()); - return &internal_client_infos[static_cast(id.class_id)]; - } -} - -void mClockScheduler::set_osd_capacity_params_from_config() -{ - uint64_t osd_bandwidth_capacity; - double osd_iop_capacity; - - std::tie(osd_bandwidth_capacity, osd_iop_capacity) = [&, this] { - if (is_rotational) { - return std::make_tuple( - cct->_conf.get_val( - "osd_mclock_max_sequential_bandwidth_hdd"), - cct->_conf.get_val("osd_mclock_max_capacity_iops_hdd")); - } else { - return std::make_tuple( - cct->_conf.get_val( - "osd_mclock_max_sequential_bandwidth_ssd"), - cct->_conf.get_val("osd_mclock_max_capacity_iops_ssd")); - } - }(); - - osd_bandwidth_capacity = std::max(1, osd_bandwidth_capacity); - osd_iop_capacity = std::max(1.0, osd_iop_capacity); - - osd_bandwidth_cost_per_io = - static_cast(osd_bandwidth_capacity) / osd_iop_capacity; - osd_bandwidth_capacity_per_shard = static_cast(osd_bandwidth_capacity) - / static_cast(num_shards); - - dout(1) << __func__ << ": osd_bandwidth_cost_per_io: " - << std::fixed << std::setprecision(2) - << osd_bandwidth_cost_per_io << " bytes/io" - << ", osd_bandwidth_capacity_per_shard " - << osd_bandwidth_capacity_per_shard << " bytes/second" - << dendl; -} - -/** - * profile_t - * - * mclock profile -- 3 params for each of 3 client classes - * 0 (min): specifies no minimum reservation - * 0 (max): specifies no upper limit - */ -struct profile_t { - struct client_config_t { - double reservation; - uint64_t weight; - double limit; - }; - client_config_t client; - client_config_t background_recovery; - client_config_t background_best_effort; -}; - -static std::ostream &operator<<( - std::ostream &lhs, const profile_t::client_config_t &rhs) -{ - return lhs << "{res: " << rhs.reservation - << ", wgt: " << rhs.weight - << ", lim: " << rhs.limit - << "}"; -} - -static std::ostream &operator<<(std::ostream &lhs, const profile_t &rhs) -{ - return lhs << "[client: " << rhs.client - << ", background_recovery: " << rhs.background_recovery - << ", background_best_effort: " << rhs.background_best_effort - << "]"; -} - -void mClockScheduler::set_config_defaults_from_profile() -{ - // Let only a single osd shard (id:0) set the profile configs - if (shard_id > 0) { - return; - } - - /** - * high_client_ops - * - * Client Allocation: - * reservation: 60% | weight: 2 | limit: 0 (max) | - * Background Recovery Allocation: - * reservation: 40% | weight: 1 | limit: 0 (max) | - * Background Best Effort Allocation: - * reservation: 0 (min) | weight: 1 | limit: 70% | - */ - static constexpr profile_t high_client_ops_profile{ - { .6, 2, 0 }, - { .4, 1, 0 }, - { 0, 1, .7 } - }; - - /** - * high_recovery_ops - * - * Client Allocation: - * reservation: 30% | weight: 1 | limit: 0 (max) | - * Background Recovery Allocation: - * reservation: 70% | weight: 2 | limit: 0 (max) | - * Background Best Effort Allocation: - * reservation: 0 (min) | weight: 1 | limit: 0 (max) | - */ - static constexpr profile_t high_recovery_ops_profile{ - { .3, 1, 0 }, - { .7, 2, 0 }, - { 0, 1, 0 } - }; - - /** - * balanced - * - * Client Allocation: - * reservation: 50% | weight: 1 | limit: 0 (max) | - * Background Recovery Allocation: - * reservation: 50% | weight: 1 | limit: 0 (max) | - * Background Best Effort Allocation: - * reservation: 0 (min) | weight: 1 | limit: 90% | - */ - static constexpr profile_t balanced_profile{ - { .5, 1, 0 }, - { .5, 1, 0 }, - { 0, 1, .9 } - }; - - const profile_t *profile = nullptr; - auto mclock_profile = cct->_conf.get_val("osd_mclock_profile"); - if (mclock_profile == "high_client_ops") { - profile = &high_client_ops_profile; - dout(10) << "Setting high_client_ops profile " << *profile << dendl; - } else if (mclock_profile == "high_recovery_ops") { - profile = &high_recovery_ops_profile; - dout(10) << "Setting high_recovery_ops profile " << *profile << dendl; - } else if (mclock_profile == "balanced") { - profile = &balanced_profile; - dout(10) << "Setting balanced profile " << *profile << dendl; - } else if (mclock_profile == "custom") { - dout(10) << "Profile set to custom, not setting defaults" << dendl; - return; - } else { - derr << "Invalid mclock profile: " << mclock_profile << dendl; - ceph_assert("Invalid choice of mclock profile" == 0); - return; - } - ceph_assert(nullptr != profile); - - auto set_config = [&conf = cct->_conf](const char *key, auto val) { - conf.set_val_default(key, std::to_string(val)); - }; - - set_config("osd_mclock_scheduler_client_res", profile->client.reservation); - set_config("osd_mclock_scheduler_client_wgt", profile->client.weight); - set_config("osd_mclock_scheduler_client_lim", profile->client.limit); - - set_config( - "osd_mclock_scheduler_background_recovery_res", - profile->background_recovery.reservation); - set_config( - "osd_mclock_scheduler_background_recovery_wgt", - profile->background_recovery.weight); - set_config( - "osd_mclock_scheduler_background_recovery_lim", - profile->background_recovery.limit); - - set_config( - "osd_mclock_scheduler_background_best_effort_res", - profile->background_best_effort.reservation); - set_config( - "osd_mclock_scheduler_background_best_effort_wgt", - profile->background_best_effort.weight); - set_config( - "osd_mclock_scheduler_background_best_effort_lim", - profile->background_best_effort.limit); - - cct->_conf.apply_changes(nullptr); -} - uint32_t mClockScheduler::calc_scaled_cost(int item_cost) { - auto cost = static_cast( - std::max( - 1, // ensure cost is non-zero and positive - item_cost)); - auto cost_per_io = static_cast(osd_bandwidth_cost_per_io); - - return std::max(cost, cost_per_io); + return mclock_conf.calc_scaled_cost(item_cost); } void mClockScheduler::update_configuration() @@ -451,7 +86,7 @@ void mClockScheduler::enqueue(OpSchedulerItem&& item) unsigned priority = item.get_priority(); // TODO: move this check into OpSchedulerItem, handle backwards compat - if (op_scheduler_class::immediate == id.class_id) { + if (SchedulerClass::immediate == id.class_id) { enqueue_high(immediate_class_priority, std::move(item)); } else if (priority >= cutoff_priority) { enqueue_high(priority, std::move(item)); @@ -468,7 +103,7 @@ void mClockScheduler::enqueue(OpSchedulerItem&& item) std::move(item), id, cost); - _get_mclock_counter(id); + mclock_conf.get_mclock_counter(id); } dout(20) << __func__ << " client_count: " << scheduler.client_count() @@ -489,7 +124,7 @@ void mClockScheduler::enqueue_front(OpSchedulerItem&& item) unsigned priority = item.get_priority(); auto id = get_scheduler_id(item); - if (op_scheduler_class::immediate == id.class_id) { + if (SchedulerClass::immediate == id.class_id) { enqueue_high(immediate_class_priority, std::move(item), true); } else if (priority >= cutoff_priority) { enqueue_high(priority, std::move(item), true); @@ -511,10 +146,10 @@ void mClockScheduler::enqueue_high(unsigned priority, } scheduler_id_t id = scheduler_id_t { - op_scheduler_class::immediate, + SchedulerClass::immediate, client_profile_id_t() }; - _get_mclock_counter(id); + mclock_conf.get_mclock_counter(id); } WorkItem mClockScheduler::dequeue() @@ -532,10 +167,10 @@ WorkItem mClockScheduler::dequeue() ceph_assert(std::get_if(&ret)); scheduler_id_t id = scheduler_id_t { - op_scheduler_class::immediate, + SchedulerClass::immediate, client_profile_id_t() }; - _put_mclock_counter(id); + mclock_conf.put_mclock_counter(id); return ret; } else { mclock_queue_t::PullReq result = scheduler.pull_request(); @@ -549,7 +184,7 @@ WorkItem mClockScheduler::dequeue() ceph_assert(result.is_retn()); auto &retn = result.get_retn(); - _put_mclock_counter(retn.client); + mclock_conf.put_mclock_counter(retn.client); return std::move(*retn.request); } } @@ -562,6 +197,7 @@ std::string mClockScheduler::display_queues() const return out.str(); } + std::vector mClockScheduler::get_tracked_keys() const noexcept { return { @@ -582,97 +218,17 @@ std::vector mClockScheduler::get_tracked_keys() const noexcept }; } + void mClockScheduler::handle_conf_change( const ConfigProxy& conf, const std::set &changed) { - if (changed.count("osd_mclock_max_capacity_iops_hdd") || - changed.count("osd_mclock_max_capacity_iops_ssd")) { - set_osd_capacity_params_from_config(); - client_registry.update_from_config( - conf, osd_bandwidth_capacity_per_shard); - } - if (changed.count("osd_mclock_max_sequential_bandwidth_hdd") || - changed.count("osd_mclock_max_sequential_bandwidth_ssd")) { - set_osd_capacity_params_from_config(); - client_registry.update_from_config( - conf, osd_bandwidth_capacity_per_shard); - } - if (changed.count("osd_mclock_profile")) { - set_config_defaults_from_profile(); - client_registry.update_from_config( - conf, osd_bandwidth_capacity_per_shard); - } - - auto get_changed_key = [&changed]() -> std::optional { - static const std::vector qos_params = { - "osd_mclock_scheduler_client_res", - "osd_mclock_scheduler_client_wgt", - "osd_mclock_scheduler_client_lim", - "osd_mclock_scheduler_background_recovery_res", - "osd_mclock_scheduler_background_recovery_wgt", - "osd_mclock_scheduler_background_recovery_lim", - "osd_mclock_scheduler_background_best_effort_res", - "osd_mclock_scheduler_background_best_effort_wgt", - "osd_mclock_scheduler_background_best_effort_lim" - }; - - for (auto &qp : qos_params) { - if (changed.count(qp)) { - return qp; - } - } - return std::nullopt; - }; - - if (auto key = get_changed_key(); key.has_value()) { - auto mclock_profile = cct->_conf.get_val("osd_mclock_profile"); - if (mclock_profile == "custom") { - client_registry.update_from_config( - conf, osd_bandwidth_capacity_per_shard); - } else { - // Attempt to change QoS parameter for a built-in profile. Restore the - // profile defaults by making one of the OSD shards remove the key from - // config monitor store. Note: monc is included in the check since the - // mock unit test currently doesn't initialize it. - if (shard_id == 0 && monc) { - static const std::vector osds = { - "osd", - "osd." + std::to_string(whoami) - }; - - for (auto osd : osds) { - std::string cmd = - "{" - "\"prefix\": \"config rm\", " - "\"who\": \"" + osd + "\", " - "\"name\": \"" + *key + "\"" - "}"; - std::vector vcmd{cmd}; - - dout(10) << __func__ << " Removing Key: " << *key - << " for " << osd << " from Mon db" << dendl; - monc->start_mon_command(vcmd, {}, nullptr, nullptr, nullptr); - } - } - } - // Alternatively, the QoS parameter, if set ephemerally for this OSD via - // the 'daemon' or 'tell' interfaces must be removed. - if (!cct->_conf.rm_val(*key)) { - dout(10) << __func__ << " Restored " << *key << " to default" << dendl; - cct->_conf.apply_changes(nullptr); - } - } + mclock_conf.mclock_handle_conf_change(conf, changed); } mClockScheduler::~mClockScheduler() { cct->_conf.remove_observer(this); - if (logger) { - cct->get_perfcounters_collection()->remove(logger); - delete logger; - logger = nullptr; - } } } diff --git a/src/osd/scheduler/mClockScheduler.h b/src/osd/scheduler/mClockScheduler.h index 8c86e0cefd3e..e94e037640a6 100644 --- a/src/osd/scheduler/mClockScheduler.h +++ b/src/osd/scheduler/mClockScheduler.h @@ -26,74 +26,13 @@ #include "osd/scheduler/OpScheduler.h" #include "common/config.h" +#include "common/mclock_common.h" #include "common/ceph_context.h" #include "osd/scheduler/OpSchedulerItem.h" -enum { - l_mclock_first = 15000, - l_mclock_immediate_queue_len, - l_mclock_client_queue_len, - l_mclock_recovery_queue_len, - l_mclock_best_effort_queue_len, - l_mclock_all_type_queue_len, - l_mclock_last, -}; - namespace ceph::osd::scheduler { -constexpr double default_min = 0.0; -constexpr double default_max = std::numeric_limits::is_iec559 ? - std::numeric_limits::infinity() : - std::numeric_limits::max(); - -/** - * client_profile_id_t - * - * client_id - global id (client.####) for client QoS - * profile_id - id generated by client's QoS profile - * - * Currently (Reef and below), both members are set to - * 0 which ensures that all external clients share the - * mClock profile allocated reservation and limit - * bandwidth. - * - * Note: Post Reef, both members will be set to non-zero - * values when the distributed feature of the mClock - * algorithm is utilized. - */ -struct client_profile_id_t { - uint64_t client_id = 0; - uint64_t profile_id = 0; - - client_profile_id_t(uint64_t _client_id, uint64_t _profile_id) : - client_id(_client_id), - profile_id(_profile_id) {} - - client_profile_id_t() = default; - - auto operator<=>(const client_profile_id_t&) const = default; - friend std::ostream& operator<<(std::ostream& out, - const client_profile_id_t& client_profile) { - out << " client_id: " << client_profile.client_id - << " profile_id: " << client_profile.profile_id; - return out; - } -}; - -struct scheduler_id_t { - op_scheduler_class class_id; - client_profile_id_t client_profile_id; - - auto operator<=>(const scheduler_id_t&) const = default; - friend std::ostream& operator<<(std::ostream& out, - const scheduler_id_t& sched_id) { - out << "{ class_id: " << sched_id.class_id - << sched_id.client_profile_id; - return out << " }"; - } -}; - /** * Scheduler implementation based on mclock. * @@ -102,87 +41,11 @@ struct scheduler_id_t { class mClockScheduler : public OpScheduler, md_config_obs_t { CephContext *cct; - const int whoami; - const uint32_t num_shards; - const int shard_id; - const bool is_rotational; const unsigned cutoff_priority; MonClient *monc; - PerfCounters *logger; - - /** - * osd_bandwidth_cost_per_io - * - * mClock expects all queued items to have a uniform expression of - * "cost". However, IO devices generally have quite different capacity - * for sequential IO vs small random IO. This implementation handles this - * by expressing all costs as a number of sequential bytes written adding - * additional cost for each random IO equal to osd_bandwidth_cost_per_io. - * - * Thus, an IO operation requiring a total of bytes to be written - * accross different locations will have a cost of - * + (osd_bandwidth_cost_per_io * ) bytes. - * - * Set in set_osd_capacity_params_from_config in the constructor and upon - * config change. - * - * Has units bytes/io. - */ - double osd_bandwidth_cost_per_io; - - /** - * osd_bandwidth_capacity_per_shard - * - * mClock expects reservation and limit paramters to be expressed in units - * of cost/second -- which means bytes/second for this implementation. - * - * Rather than expecting users to compute appropriate limit and reservation - * values for each class of OSDs in their cluster, we instead express - * reservation and limit paramaters as ratios of the OSD's maxmimum capacity. - * osd_bandwidth_capacity_per_shard is that capacity divided by the number - * of shards. - * - * Set in set_osd_capacity_params_from_config in the constructor and upon - * config change. - * - * This value gets passed to ClientRegistry::update_from_config in order - * to resolve the full reservaiton and limit parameters for mclock from - * the configured ratios. - * - * Has units bytes/second. - */ - double osd_bandwidth_capacity_per_shard; - - class ClientRegistry { - std::array< - crimson::dmclock::ClientInfo, - static_cast(op_scheduler_class::immediate) - > internal_client_infos = { - // Placeholder, gets replaced with configured values - crimson::dmclock::ClientInfo(1, 1, 1), - crimson::dmclock::ClientInfo(1, 1, 1) - }; - - crimson::dmclock::ClientInfo default_external_client_info = {1, 1, 1}; - std::map external_client_infos; - const crimson::dmclock::ClientInfo *get_external_client( - const client_profile_id_t &client) const; - public: - /** - * update_from_config - * - * Sets the mclock paramaters (reservation, weight, and limit) - * for each class of IO (background_recovery, background_best_effort, - * and client). - */ - void update_from_config( - const ConfigProxy &conf, - double capacity_per_shard); - const crimson::dmclock::ClientInfo *get_info( - const scheduler_id_t &id) const; - } client_registry; + ClientRegistry client_registry; + MclockConfig mclock_conf; using mclock_queue_t = crimson::dmclock::PullPriorityQueue< scheduler_id_t, OpSchedulerItem, @@ -210,23 +73,6 @@ class mClockScheduler : public OpScheduler, md_config_obs_t { }; } - /** - * set_osd_capacity_params_from_config - * - * mClockScheduler uses two parameters, osd_bandwidth_cost_per_io - * and osd_bandwidth_capacity_per_shard, internally. These two - * parameters are derived from config parameters - * osd_mclock_max_capacity_iops_(hdd|ssd) and - * osd_mclock_max_sequential_bandwidth_(hdd|ssd) as well as num_shards. - * Invoking set_osd_capacity_params_from_config() resets those derived - * params based on the current config and should be invoked any time they - * are modified as well as in the constructor. See handle_conf_change(). - */ - void set_osd_capacity_params_from_config(); - - // Set the mclock related config params based on the profile - void set_config_defaults_from_profile(); - public: template mClockScheduler( @@ -238,15 +84,12 @@ public: MonClient *monc, bool init_perfcounter=true) : cct(cct), - whoami(whoami), - num_shards(num_shards), - shard_id(shard_id), - is_rotational(is_rotational), cutoff_priority(cutoff_priority), monc(monc), - logger(nullptr), + mclock_conf(cct, client_registry, monc, num_shards, + is_rotational, shard_id, whoami), scheduler( - std::bind(&mClockScheduler::ClientRegistry::get_info, + std::bind(&ClientRegistry::get_info, &client_registry, std::placeholders::_1), idle_age, erase_age, check_time, @@ -255,13 +98,13 @@ public: { cct->_conf.add_observer(this); ceph_assert(num_shards > 0); - set_osd_capacity_params_from_config(); - set_config_defaults_from_profile(); + mclock_conf.set_osd_capacity_params_from_config(); + mclock_conf.set_config_defaults_from_profile(); client_registry.update_from_config( - cct->_conf, osd_bandwidth_capacity_per_shard); + cct->_conf, mclock_conf.get_capacity_per_shard()); if (init_perfcounter) { - _init_logger(); + mclock_conf.init_logger(); } } mClockScheduler( @@ -318,14 +161,11 @@ public: const std::set &changed) final; double get_cost_per_io() const { - return osd_bandwidth_cost_per_io; + return mclock_conf.get_cost_per_io(); } private: // Enqueue the op to the high priority queue void enqueue_high(unsigned prio, OpSchedulerItem &&item, bool front = false); - void _init_logger(); - void _get_mclock_counter(scheduler_id_t id); - void _put_mclock_counter(scheduler_id_t id); }; } diff --git a/src/test/osd/TestMClockScheduler.cc b/src/test/osd/TestMClockScheduler.cc index db016b2f9bdc..e283a2b3bd26 100644 --- a/src/test/osd/TestMClockScheduler.cc +++ b/src/test/osd/TestMClockScheduler.cc @@ -7,6 +7,7 @@ #include "global/global_context.h" #include "global/global_init.h" #include "common/common_init.h" +#include "common/mclock_common.h" #include "osd/scheduler/mClockScheduler.h" #include "osd/scheduler/OpSchedulerItem.h" @@ -59,14 +60,14 @@ public: {} struct MockDmclockItem : public PGOpQueueable { - op_scheduler_class scheduler_class; + SchedulerClass scheduler_class; - MockDmclockItem(op_scheduler_class _scheduler_class) : + MockDmclockItem(SchedulerClass _scheduler_class) : PGOpQueueable(spg_t()), scheduler_class(_scheduler_class) {} MockDmclockItem() - : MockDmclockItem(op_scheduler_class::background_best_effort) {} + : MockDmclockItem(SchedulerClass::background_best_effort) {} ostream &print(ostream &rhs) const final { return rhs; } @@ -78,7 +79,7 @@ public: return std::nullopt; } - op_scheduler_class get_scheduler_class() const final { + SchedulerClass get_scheduler_class() const final { return scheduler_class; } @@ -118,7 +119,7 @@ TEST_F(mClockSchedulerTest, TestEmpty) { ASSERT_TRUE(q.empty()); for (unsigned i = 100; i < 105; i+=2) { - q.enqueue(create_item(i, client1, op_scheduler_class::client)); + q.enqueue(create_item(i, client1, SchedulerClass::client)); std::this_thread::sleep_for(std::chrono::microseconds(1)); } @@ -151,7 +152,7 @@ TEST_F(mClockSchedulerTest, TestSingleClientOrderedEnqueueDequeue) { ASSERT_TRUE(q.empty()); for (unsigned i = 100; i < 105; ++i) { - q.enqueue(create_item(i, client1, op_scheduler_class::client)); + q.enqueue(create_item(i, client1, SchedulerClass::client)); std::this_thread::sleep_for(std::chrono::microseconds(1)); } @@ -175,7 +176,7 @@ TEST_F(mClockSchedulerTest, TestMultiClientOrderedEnqueueDequeue) { const unsigned NUM = 1000; for (unsigned i = 0; i < NUM; ++i) { for (auto &&c: {client1, client2, client3}) { - q.enqueue(create_item(i, c, op_scheduler_class::client)); + q.enqueue(create_item(i, c, SchedulerClass::client)); std::this_thread::sleep_for(std::chrono::microseconds(1)); } } @@ -199,7 +200,7 @@ TEST_F(mClockSchedulerTest, TestMultiClientOrderedEnqueueDequeue) { TEST_F(mClockSchedulerTest, TestHighPriorityQueueEnqueueDequeue) { ASSERT_TRUE(q.empty()); for (unsigned i = 200; i < 205; ++i) { - q.enqueue(create_high_prio_item(i, i, client1, op_scheduler_class::client)); + q.enqueue(create_high_prio_item(i, i, client1, SchedulerClass::client)); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } @@ -228,19 +229,19 @@ TEST_F(mClockSchedulerTest, TestAllQueuesEnqueueDequeue) { // Insert ops into the mClock queue for (unsigned i = 100; i < 102; ++i) { - q.enqueue(create_item(i, client1, op_scheduler_class::client)); + q.enqueue(create_item(i, client1, SchedulerClass::client)); std::this_thread::sleep_for(std::chrono::microseconds(1)); } // Insert Immediate ops for (unsigned i = 103; i < 105; ++i) { - q.enqueue(create_item(i, client1, op_scheduler_class::immediate)); + q.enqueue(create_item(i, client1, SchedulerClass::immediate)); std::this_thread::sleep_for(std::chrono::microseconds(1)); } // Insert ops into the high queue for (unsigned i = 200; i < 202; ++i) { - q.enqueue(create_high_prio_item(i, i, client1, op_scheduler_class::client)); + q.enqueue(create_high_prio_item(i, i, client1, SchedulerClass::client)); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } @@ -278,11 +279,11 @@ TEST_F(mClockSchedulerTest, TestSlowDequeue) { // Insert ops into the mClock queue unsigned i = 0; for (; i < 100; ++i) { - q.enqueue(create_item(i, client1, op_scheduler_class::background_best_effort)); + q.enqueue(create_item(i, client1, SchedulerClass::background_best_effort)); std::this_thread::sleep_for(5ms); } for (; i < 200; ++i) { - q.enqueue(create_item(i, client2, op_scheduler_class::client)); + q.enqueue(create_item(i, client2, SchedulerClass::client)); std::this_thread::sleep_for(5ms); }