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)
--- /dev/null
+// -*- 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 <memory>
+#include <functional>
+
+#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<size_t>(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 <cost>/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<double>(
+ "osd_mclock_scheduler_client_res");
+ double lim = conf.get_val<double>(
+ "osd_mclock_scheduler_client_lim");
+ uint64_t wgt = conf.get_val<uint64_t>(
+ "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<double>(
+ "osd_mclock_scheduler_background_recovery_res");
+ lim = conf.get_val<double>(
+ "osd_mclock_scheduler_background_recovery_lim");
+ wgt = conf.get_val<uint64_t>(
+ "osd_mclock_scheduler_background_recovery_wgt");
+ internal_client_infos[
+ static_cast<size_t>(SchedulerClass::background_recovery)].update(
+ get_res(res),
+ wgt,
+ get_lim(lim));
+
+ // Set background best effort client infos
+ res = conf.get_val<double>(
+ "osd_mclock_scheduler_background_best_effort_res");
+ lim = conf.get_val<double>(
+ "osd_mclock_scheduler_background_best_effort_lim");
+ wgt = conf.get_val<uint64_t>(
+ "osd_mclock_scheduler_background_best_effort_wgt");
+ internal_client_infos[
+ static_cast<size_t>(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<size_t>(id.class_id) < internal_client_infos.size());
+ return &internal_client_infos[static_cast<size_t>(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<std::string>("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<Option::size_t>(
+ "osd_mclock_max_sequential_bandwidth_hdd"),
+ cct->_conf.get_val<double>("osd_mclock_max_capacity_iops_hdd"));
+ } else {
+ return std::make_tuple(
+ cct->_conf.get_val<Option::size_t>(
+ "osd_mclock_max_sequential_bandwidth_ssd"),
+ cct->_conf.get_val<double>("osd_mclock_max_capacity_iops_ssd"));
+ }
+ }();
+
+ osd_bandwidth_capacity = std::max<uint64_t>(1, osd_bandwidth_capacity);
+ osd_iop_capacity = std::max<double>(1.0, osd_iop_capacity);
+
+ osd_bandwidth_cost_per_io =
+ static_cast<double>(osd_bandwidth_capacity) / osd_iop_capacity;
+ osd_bandwidth_capacity_per_shard =
+ static_cast<double>(osd_bandwidth_capacity) /
+ static_cast<double>(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<uint32_t>(
+ std::max<int>(
+ 1, // ensure cost is non-zero and positive
+ item_cost));
+ auto cost_per_io = static_cast<uint32_t>(osd_bandwidth_cost_per_io);
+
+ return std::max<uint32_t>(cost, cost_per_io);
+}
+
+void MclockConfig::mclock_handle_conf_change(const ConfigProxy& conf,
+ const std::set<std::string>
+ &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<std::string> {
+ static const std::vector<std::string> 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<std::string>("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<std::string> osds = {
+ "osd",
+ "osd." + std::to_string(whoami)
+ };
+
+ for (auto osd : osds) {
+ std::string cmd =
+ "{"
+ "\"prefix\": \"config rm\", "
+ "\"who\": \"" + osd + "\", "
+ "\"name\": \"" + *key + "\""
+ "}";
+ std::vector<std::string> 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;
+ }
+}
--- /dev/null
+// -*- 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<double>::is_iec559 ?
+ std::numeric_limits<double>::infinity() :
+ std::numeric_limits<double>::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<size_t>(SchedulerClass::background_best_effort) + 1;
+ std::vector<crimson::dmclock::ClientInfo> internal_client_infos;
+
+ crimson::dmclock::ClientInfo default_external_client_info = {1, 1, 1};
+ std::map<client_profile_id_t,
+ crimson::dmclock::ClientInfo> 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<std::string> &changed);
+ uint32_t calc_scaled_cost(int item_cost);
+};
namespace ceph::osd::scheduler {
-std::ostream& operator<<(std::ostream& out, const op_scheduler_class& class_id) {
- out << static_cast<size_t>(class_id);
- return out;
-}
-
void PGOpItem::run(
OSD *osd,
OSDShard *sdata,
#include "osd/PG.h"
#include "osd/PGPeeringEvent.h"
#include "messages/MOSDOp.h"
+#include "common/mclock_common.h"
class OSD;
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
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) {
return qitem->peering_requires_pg();
}
- op_scheduler_class get_scheduler_class() const {
+ SchedulerClass get_scheduler_class() const {
return qitem->get_scheduler_class();
}
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;
}
}
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;
}
}
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;
}
};
}
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;
}
};
}
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;
}
};
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;
}
};
}
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);
}
};
}
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);
}
};
}
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;
}
};
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());
}
{
// matching existing op_scheduler_item_t::operator<<() format
using class_t =
- std::underlying_type_t<ceph::osd::scheduler::op_scheduler_class>;
+ std::underlying_type_t<SchedulerClass>;
const auto qos_cost = opsi.was_queued_via_mclock()
? fmt::format(" qos_cost {}", opsi.qos_cost)
: "";
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 <cost>/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<double>(
- "osd_mclock_scheduler_client_res");
- double lim = conf.get_val<double>(
- "osd_mclock_scheduler_client_lim");
- uint64_t wgt = conf.get_val<uint64_t>(
- "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<double>(
- "osd_mclock_scheduler_background_recovery_res");
- lim = conf.get_val<double>(
- "osd_mclock_scheduler_background_recovery_lim");
- wgt = conf.get_val<uint64_t>(
- "osd_mclock_scheduler_background_recovery_wgt");
- internal_client_infos[
- static_cast<size_t>(op_scheduler_class::background_recovery)].update(
- get_res(res),
- wgt,
- get_lim(lim));
-
- // Set background best effort client infos
- res = conf.get_val<double>(
- "osd_mclock_scheduler_background_best_effort_res");
- lim = conf.get_val<double>(
- "osd_mclock_scheduler_background_best_effort_lim");
- wgt = conf.get_val<uint64_t>(
- "osd_mclock_scheduler_background_best_effort_wgt");
- internal_client_infos[
- static_cast<size_t>(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<size_t>(id.class_id) < internal_client_infos.size());
- return &internal_client_infos[static_cast<size_t>(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<Option::size_t>(
- "osd_mclock_max_sequential_bandwidth_hdd"),
- cct->_conf.get_val<double>("osd_mclock_max_capacity_iops_hdd"));
- } else {
- return std::make_tuple(
- cct->_conf.get_val<Option::size_t>(
- "osd_mclock_max_sequential_bandwidth_ssd"),
- cct->_conf.get_val<double>("osd_mclock_max_capacity_iops_ssd"));
- }
- }();
-
- osd_bandwidth_capacity = std::max<uint64_t>(1, osd_bandwidth_capacity);
- osd_iop_capacity = std::max<double>(1.0, osd_iop_capacity);
-
- osd_bandwidth_cost_per_io =
- static_cast<double>(osd_bandwidth_capacity) / osd_iop_capacity;
- osd_bandwidth_capacity_per_shard = static_cast<double>(osd_bandwidth_capacity)
- / static_cast<double>(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<std::string>("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<uint32_t>(
- std::max<int>(
- 1, // ensure cost is non-zero and positive
- item_cost));
- auto cost_per_io = static_cast<uint32_t>(osd_bandwidth_cost_per_io);
-
- return std::max<uint32_t>(cost, cost_per_io);
+ return mclock_conf.calc_scaled_cost(item_cost);
}
void mClockScheduler::update_configuration()
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));
std::move(item),
id,
cost);
- _get_mclock_counter(id);
+ mclock_conf.get_mclock_counter(id);
}
dout(20) << __func__ << " client_count: " << scheduler.client_count()
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);
}
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()
ceph_assert(std::get_if<OpSchedulerItem>(&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();
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);
}
}
return out.str();
}
+
std::vector<std::string> mClockScheduler::get_tracked_keys() const noexcept
{
return {
};
}
+
void mClockScheduler::handle_conf_change(
const ConfigProxy& conf,
const std::set<std::string> &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<std::string> {
- static const std::vector<std::string> 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<std::string>("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<std::string> osds = {
- "osd",
- "osd." + std::to_string(whoami)
- };
-
- for (auto osd : osds) {
- std::string cmd =
- "{"
- "\"prefix\": \"config rm\", "
- "\"who\": \"" + osd + "\", "
- "\"name\": \"" + *key + "\""
- "}";
- std::vector<std::string> 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;
- }
}
}
#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<double>::is_iec559 ?
- std::numeric_limits<double>::infinity() :
- std::numeric_limits<double>::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.
*
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 <size> bytes to be written
- * accross <iops> different locations will have a cost of
- * <size> + (osd_bandwidth_cost_per_io * <iops>) 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<size_t>(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<client_profile_id_t,
- crimson::dmclock::ClientInfo> 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,
};
}
- /**
- * 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<typename Rep, typename Per>
mClockScheduler(
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,
{
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(
const std::set<std::string> &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);
};
}
#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"
{}
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; }
return std::nullopt;
}
- op_scheduler_class get_scheduler_class() const final {
+ SchedulerClass get_scheduler_class() const final {
return scheduler_class;
}
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));
}
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));
}
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));
}
}
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));
}
// 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));
}
// 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);
}