]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
crimson/os/seastore: measure transactional efforts that are discarded or committed
authorYingxin Cheng <yingxin.cheng@intel.com>
Thu, 15 Jul 2021 02:26:02 +0000 (10:26 +0800)
committerYingxin Cheng <yingxin.cheng@intel.com>
Tue, 20 Jul 2021 08:44:16 +0000 (16:44 +0800)
The efforts of a transaction include the number and bytes of its read,
mutate, retire and fresh extents, and the bytes of delta generated.

This helps to understand the following aspects:
* The ratio of discarded efforts vs committed efforts;
* The average efforts of a transaction;
* The distribution of read/mutate/delta/retire/fresh efforts;
* The memory overhead and potential disk overhead of a transaction;
* How early a transaction invalidation happens;
* The average extent length;

It is possible to extend the effort metrics to be labeled by extent
types, in case we want to distinguish and profile the efforts at the
sub-component level.

Signed-off-by: Yingxin Cheng <yingxin.cheng@intel.com>
src/crimson/os/seastore/cache.cc
src/crimson/os/seastore/cache.h

index e1727e0aac90767609790b57ad14bf0df6e7964f..eab2d0802963db6c967297fb35987295f9da5751 100644 (file)
@@ -175,9 +175,11 @@ void Cache::register_metrics()
       }
     );
   };
-  for (auto& src : {src_t::MUTATE,
-                    src_t::INIT,
-                    src_t::CLEANER}) {
+  for (auto& [src, label] : labels_by_src) {
+    if (src == src_t::READ) {
+      // READ transaction won't commit
+      continue;
+    }
     register_trans_committed(src);
   }
 
@@ -316,6 +318,89 @@ void Cache::register_metrics()
       ),
     }
   );
+
+  {
+    /*
+     * efforts discarded/committed
+     *
+     * XXX: include ext_label if want to measure efforts at the granularity of
+     * sub-components.
+     */
+    auto effort_label = sm::label("effort");
+    std::map<std::string, sm::label_instance> labels_by_effort {
+      {"READ",   effort_label("READ")},
+      {"MUTATE", effort_label("MUTATE")},
+      {"RETIRE", effort_label("RETIRE")},
+      {"FRESH",  effort_label("FRESH")},
+    };
+
+    auto counter_label = sm::label("counter");
+    std::map<std::string, sm::label_instance> labels_by_counter {
+      {"EXTENTS", counter_label("EXTENTS")},
+      {"BYTES", counter_label("BYTES")},
+      {"DELTA_BYTES", counter_label("DELTA_BYTES")},
+    };
+
+    auto register_effort =
+      [this, &labels_by_src, &labels_by_effort, &labels_by_counter]
+      (const char* category,
+       src_t src,
+       const std::string& effort_name,
+       const char* counter_name,
+       uint64_t& value) {
+        std::ostringstream oss_desc;
+        oss_desc << "total number of " << category
+                 << " transactional efforts labeled by source, effort and counter";
+        std::ostringstream oss_metric;
+        oss_metric << category << "_efforts";
+        metrics.add_group(
+          "cache",
+          {
+            sm::make_counter(
+              oss_metric.str(),
+              value,
+              sm::description(oss_desc.str()),
+              {labels_by_src.find(src)->second,
+               labels_by_effort.find(effort_name)->second,
+               labels_by_counter.find(counter_name)->second}
+            ),
+          }
+        );
+      };
+
+    auto get_efforts_by_category =
+      [this](const char* category) -> auto& {
+        if (strcmp(category, "committed") == 0) {
+          return stats.committed_efforts_by_src;
+        } else {
+          assert(strcmp(category, "invalidated") == 0);
+          return stats.invalidated_efforts_by_src;
+        }
+      };
+
+    for (auto& category : {"committed", "invalidated"}) {
+      auto& efforts_by_src = get_efforts_by_category(category);
+      for (auto& [src, label] : labels_by_src) {
+        if (std::strcmp(category, "committed") == 0 && src == src_t::READ) {
+          // READ transaction won't commit
+          continue;
+        }
+
+        auto& efforts = get_counter(efforts_by_src, src);
+        for (auto& [effort_name, _label] : labels_by_effort) {
+          auto& effort = efforts.get_by_name(effort_name);
+          for (auto& counter_name : {"EXTENTS", "BYTES"}) {
+            auto& value = effort.get_by_name(counter_name);
+            register_effort(category, src, effort_name, counter_name, value);
+          }
+          if (effort_name == "MUTATE") {
+            register_effort(category, src, effort_name, "DELTA_BYTES",
+                            efforts.mutate_delta_bytes);
+          }
+        } // effort_name
+      } // src
+    } // category
+  }
 }
 
 void Cache::add_extent(CachedExtentRef ref)
@@ -441,6 +526,40 @@ void Cache::invalidate(Transaction& t, CachedExtent& conflicting_extent)
       t.get_src(), conflicting_extent.get_type());
   assert(stats.trans_invalidated.count(m_key));
   ++(stats.trans_invalidated[m_key]);
+  auto& efforts = get_counter(stats.invalidated_efforts_by_src,
+                              t.get_src());
+  measure_efforts(t, efforts);
+  for (auto &i: t.mutated_block_list) {
+    if (!i->is_valid()) {
+      continue;
+    }
+    ++efforts.mutate.extents;
+    efforts.mutate.bytes += i->get_length();
+    efforts.mutate_delta_bytes += i->get_delta().length();
+  }
+}
+
+void Cache::measure_efforts(Transaction& t, trans_efforts_t& efforts)
+{
+  efforts.read.extents += t.read_set.size();
+  for (auto &i: t.read_set) {
+    efforts.read.bytes += i.ref->get_length();
+  }
+
+  efforts.retire.extents += t.retired_set.size();
+  for (auto &i: t.retired_set) {
+    efforts.retire.bytes += i->get_length();
+  }
+
+  efforts.fresh.extents += t.fresh_block_list.size();
+  for (auto &i: t.fresh_block_list) {
+    efforts.fresh.bytes += i->get_length();
+  }
+
+  /**
+   * Mutated blocks are special because CachedExtent::get_delta() is not
+   * idempotent, so they need to be dealt later.
+   */
 }
 
 CachedExtentRef Cache::alloc_new_extent_by_type(
@@ -514,6 +633,9 @@ record_t Cache::prepare_record(Transaction &t)
   assert(!t.is_weak());
   assert(t.get_src() != Transaction::src_t::READ);
   ++(get_counter(stats.trans_committed_by_src, t.get_src()));
+  auto& efforts = get_counter(stats.committed_efforts_by_src,
+                              t.get_src());
+  measure_efforts(t, efforts);
 
   // Should be valid due to interruptible future
   for (auto &i: t.read_set) {
@@ -534,6 +656,8 @@ record_t Cache::prepare_record(Transaction &t)
       continue;
     }
     DEBUGT("mutating {}", t, *i);
+    ++efforts.mutate.extents;
+    efforts.mutate.bytes += i->get_length();
 
     assert(i->prior_instance);
     replace_extent(i, i->prior_instance);
@@ -573,7 +697,9 @@ record_t Cache::prepare_record(Transaction &t)
        });
       i->last_committed_crc = final_crc;
     }
-    assert(record.deltas.back().bl.length());
+    auto delta_length = record.deltas.back().bl.length();
+    assert(delta_length);
+    efforts.mutate_delta_bytes += delta_length;
   }
 
   // Transaction is now a go, set up in-memory cache state
index f7389158d4c21c40fd694b8a07e1666d8e24922f..7413d5a513cad743536dcf08c633ce96ddb8320d 100644 (file)
@@ -590,16 +590,65 @@ private:
     uint64_t hit;
   };
 
+  /**
+   * effort_t
+   *
+   * Count the number of extents involved in the effort and the total bytes of
+   * them.
+   *
+   * Each effort_t represents the effort of a set of extents involved in the
+   * transaction, classified by read, mutate, retire and allocate behaviors,
+   * see trans_efforts_t.
+   */
+  struct effort_t {
+    uint64_t extents = 0;
+    uint64_t bytes = 0;
+
+    uint64_t& get_by_name(const std::string& counter_name) {
+      if (counter_name == "EXTENTS") {
+        return extents;
+      } else {
+        ceph_assert(counter_name == "BYTES");
+        return bytes;
+      }
+    }
+  };
+
+  struct trans_efforts_t {
+    effort_t read;
+    effort_t mutate;
+    uint64_t mutate_delta_bytes = 0;
+    effort_t retire;
+    effort_t fresh;
+
+    effort_t& get_by_name(const std::string& effort_name) {
+      if (effort_name == "READ") {
+        return read;
+      } else if (effort_name == "MUTATE") {
+        return mutate;
+      } else if (effort_name == "RETIRE") {
+        return retire;
+      } else {
+        ceph_assert(effort_name == "FRESH");
+        return fresh;
+      }
+    }
+  };
+
   struct {
     std::array<uint64_t, Transaction::SRC_MAX> trans_created_by_src;
     std::array<uint64_t, Transaction::SRC_MAX> trans_committed_by_src;
+    std::array<trans_efforts_t, Transaction::SRC_MAX> committed_efforts_by_src;
     std::unordered_map<src_ext_t, uint64_t,
                        boost::hash<src_ext_t>> trans_invalidated;
+    std::array<trans_efforts_t, Transaction::SRC_MAX> invalidated_efforts_by_src;
     std::unordered_map<src_ext_t, query_counters_t,
                        boost::hash<src_ext_t>> cache_query;
   } stats;
-  uint64_t& get_counter(
-      std::array<uint64_t, Transaction::SRC_MAX>& counters_by_src,
+
+  template <typename CounterT>
+  CounterT& get_counter(
+      std::array<CounterT, Transaction::SRC_MAX>& counters_by_src,
       Transaction::src_t src) {
     assert(static_cast<std::size_t>(src) < counters_by_src.size());
     return counters_by_src[static_cast<std::size_t>(src)];
@@ -644,6 +693,9 @@ private:
   /// Mark a valid transaction as conflicted
   void invalidate(Transaction& t, CachedExtent& conflicting_extent);
 
+  /// Measure efforts of a submitting/invalidating transaction
+  void measure_efforts(Transaction& t, trans_efforts_t& efforts);
+
   template <typename T>
   get_extent_ret<T> read_extent(
     TCachedExtentRef<T>&& extent