--- /dev/null
+#!/usr/bin/env bash
+#
+# Copyright (C) 2022 Red Hat <contact@redhat.com>
+#
+# Author: Sridhar Seshasayee <sseshasa@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Library Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library Public License for more details.
+#
+
+source $CEPH_ROOT/qa/standalone/ceph-helpers.sh
+
+function run() {
+ local dir=$1
+ shift
+
+ export CEPH_MON="127.0.0.1:7124" # git grep '\<7124\>' : there must be only one
+ export CEPH_ARGS
+ CEPH_ARGS+="--fsid=$(uuidgen) --auth-supported=none "
+ CEPH_ARGS+="--mon-host=$CEPH_MON "
+ CEPH_ARGS+="--debug-bluestore 20 "
+
+ local funcs=${@:-$(set | sed -n -e 's/^\(TEST_[0-9a-z_]*\) .*/\1/p')}
+ for func in $funcs ; do
+ setup $dir || return 1
+ $func $dir || return 1
+ teardown $dir || return 1
+ done
+}
+
+function TEST_snaptrim_stats() {
+ local dir=$1
+ local poolname=test
+ local OSDS=3
+ local PGNUM=8
+ local PGPNUM=8
+ local objects=10
+ local WAIT_FOR_UPDATE=10
+
+ setup $dir || return 1
+ run_mon $dir a --osd_pool_default_size=$OSDS || return 1
+ run_mgr $dir x || return 1
+ for osd in $(seq 0 $(expr $OSDS - 1))
+ do
+ run_osd $dir $osd --osd_pool_default_pg_autoscale_mode=off || return 1
+ done
+
+ # disable scrubs
+ ceph osd set noscrub || return 1
+ ceph osd set nodeep-scrub || return 1
+
+ # Create a pool
+ create_pool $poolname $PGNUM $PGPNUM
+ wait_for_clean || return 1
+ poolid=$(ceph osd dump | grep "^pool.*[']${poolname}[']" | awk '{ print $2 }')
+
+ # write a few objects
+ TESTDATA="testdata.1"
+ dd if=/dev/urandom of=$TESTDATA bs=4096 count=1
+ for i in `seq 1 $objects`
+ do
+ rados -p $poolname put obj${i} $TESTDATA
+ done
+ rm -f $TESTDATA
+
+ # create a snapshot, clones
+ SNAP=1
+ rados -p $poolname mksnap snap${SNAP}
+ TESTDATA="testdata.2"
+ dd if=/dev/urandom of=$TESTDATA bs=4096 count=1
+ for i in `seq 1 $objects`
+ do
+ rados -p $poolname put obj${i} $TESTDATA
+ done
+ rm -f $TESTDATA
+
+ # remove the snapshot, should trigger snaptrim
+ rados -p $poolname rmsnap snap${SNAP}
+
+ # check for snaptrim stats
+ wait_for_clean || return 1
+ sleep $WAIT_FOR_UPDATE
+ local objects_trimmed=0
+ for i in $(seq 0 $(expr $PGNUM - 1))
+ do
+ local pgid="${poolid}.${i}"
+ objects_trimmed=$(expr $objects_trimmed + $(ceph pg $pgid query | \
+ jq '.info.stats.objects_trimmed'))
+ done
+
+ test $objects_trimmed -eq $objects || return 1
+
+ teardown $dir || return 1
+}
+
+main test-snaptrim-stats "$@"
+
+# Local Variables:
+# compile-command: "cd build ; make -j4 && \
+# ../qa/run-standalone.sh test-snaptrim-stats.sh"
+# End:
tab.define_column("LAST_SCRUB_DURATION", TextTable::LEFT, TextTable::RIGHT);
tab.define_column("SCRUB_SCHEDULING", TextTable::LEFT, TextTable::LEFT);
tab.define_column("OBJECTS_SCRUBBED", TextTable::LEFT, TextTable::RIGHT);
+ tab.define_column("OBJECTS_TRIMMED", TextTable::LEFT, TextTable::RIGHT);
}
for (const auto& [pg, st] : pg_stats) {
<< st.last_scrub_duration
<< st.dump_scrub_schedule()
<< st.objects_scrubbed
+ << st.objects_trimmed
<< TextTable::endrow;
}
}
uint64_t get_snap_trimq_size() const override {
return snap_trimq.size();
}
+
+ static void add_objects_trimmed_count(
+ int64_t count, pg_stat_t &stats) {
+ stats.objects_trimmed += count;
+ }
+
+ void add_objects_trimmed_count(int64_t count) {
+ recovery_state.update_stats_wo_resched(
+ [=](auto &history, auto &stats) {
+ add_objects_trimmed_count(count, stats);
+ });
+ }
+
+ static void reset_objects_trimmed(pg_stat_t &stats) {
+ stats.objects_trimmed = 0;
+ }
+
+ void reset_objects_trimmed() {
+ recovery_state.update_stats_wo_resched(
+ [=](auto &history, auto &stats) {
+ reset_objects_trimmed(stats);
+ });
+ }
+
unsigned get_target_pg_log_entries() const override;
void clear_publish_stats() override;
child->info.stats.parent_split_bits = split_bits;
info.stats.stats_invalid = true;
child->info.stats.stats_invalid = true;
+ child->info.stats.objects_trimmed = 0;
child->info.last_epoch_started = info.last_epoch_started;
child->info.last_interval_started = info.last_interval_started;
PGTransaction *t = ctx->op_t.get();
+ int64_t num_objects_before_trim = ctx->delta_stats.num_objects;
+
if (new_snaps.empty()) {
// remove clone
dout(10) << coid << " snaps " << old_snaps << " -> "
t->setattrs(head_oid, attrs);
}
+ // Stats reporting - Set number of objects trimmed
+ if (num_objects_before_trim > ctx->delta_stats.num_objects) {
+ int64_t num_objects_trimmed =
+ num_objects_before_trim - ctx->delta_stats.num_objects;
+ add_objects_trimmed_count(num_objects_trimmed);
+ }
+
*ctxp = std::move(ctx);
return 0;
}
dout(10) << __func__ << ": nosnaptrim set, not kicking" << dendl;
} else {
dout(10) << __func__ << ": clean and snaps to trim, kicking" << dendl;
+ reset_objects_trimmed();
snap_trimmer_machine.process_event(KickTrim());
}
}
f->dump_int("last_scrub_duration", last_scrub_duration);
f->dump_string("scrub_schedule", dump_scrub_schedule());
f->dump_float("scrub_duration", scrub_duration);
+ f->dump_int("objects_trimmed", objects_trimmed);
stats.dump(f);
f->open_array_section("up");
for (auto p = up.cbegin(); p != up.cend(); ++p)
void pg_stat_t::encode(ceph::buffer::list &bl) const
{
- ENCODE_START(27, 22, bl);
+ ENCODE_START(28, 22, bl);
encode(version, bl);
encode(reported_seq, bl);
encode(reported_epoch, bl);
encode(scrub_sched_status.m_is_periodic, bl);
encode(objects_scrubbed, bl);
encode(scrub_duration, bl);
+ encode(objects_trimmed, bl);
ENCODE_FINISH(bl);
}
{
bool tmp;
uint32_t old_state;
- DECODE_START(27, bl);
+ DECODE_START(28, bl);
decode(version, bl);
decode(reported_seq, bl);
decode(reported_epoch, bl);
decode(objects_scrubbed, bl);
decode(scrub_duration, bl);
}
+ if (struct_v >= 28) {
+ decode(objects_trimmed, bl);
+ }
}
DECODE_FINISH(bl);
}
a.scrub_duration = 0.003;
a.snaptrimq_len = 1048576;
a.objects_scrubbed = 0;
+ a.objects_trimmed = 0;
list<object_stat_collection_t*> l;
object_stat_collection_t::generate_test_instances(l);
a.stats = *l.back();
l.last_scrub_duration == r.last_scrub_duration &&
l.scrub_sched_status == r.scrub_sched_status &&
l.objects_scrubbed == r.objects_scrubbed &&
- l.scrub_duration == r.scrub_duration;
+ l.scrub_duration == r.scrub_duration &&
+ l.objects_trimmed == r.objects_trimmed;
}
// -- store_statfs_t --
// snaptrimq.size() is 64bit, but let's be serious - anything over 50k is
// absurd already, so cap it to 2^32 and save 4 bytes at the same time
uint32_t snaptrimq_len;
+ int64_t objects_trimmed;
pg_scrubbing_status_t scrub_sched_status;
up_primary(-1),
acting_primary(-1),
snaptrimq_len(0),
+ objects_trimmed(0),
stats_invalid(false),
dirty_stats_invalid(false),
omap_stats_invalid(false),
snapshots_cxx.cc)
target_link_libraries(ceph_test_rados_api_snapshots_pp
librados ${UNITTEST_LIBS} radostest-cxx)
+add_executable(ceph_test_rados_api_snapshots_stats
+ snapshots_stats.cc)
+target_link_libraries(ceph_test_rados_api_snapshots_stats
+ librados ${UNITTEST_LIBS} radostest)
+add_executable(ceph_test_rados_api_snapshots_stats_pp
+ snapshots_stats_cxx.cc)
+target_link_libraries(ceph_test_rados_api_snapshots_stats_pp
+ librados ${UNITTEST_LIBS} radostest-cxx)
add_executable(ceph_test_rados_api_cls_remote_reads
cls_remote_reads.cc
--- /dev/null
+#include "include/rados.h"
+#include "json_spirit/json_spirit.h"
+#include "test/librados/test.h"
+#include "test/librados/TestCase.h"
+
+#include <algorithm>
+#include <errno.h>
+#include "gtest/gtest.h"
+#include <string>
+#include <vector>
+
+using std::string;
+
+class LibRadosSnapshotStatsSelfManaged : public RadosTest {
+public:
+ LibRadosSnapshotStatsSelfManaged() {};
+ ~LibRadosSnapshotStatsSelfManaged() override {};
+protected:
+ void SetUp() override {
+ // disable pg autoscaler for the tests
+ string c =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"off\""
+ "}";
+ char *cmd[1];
+ cmd[0] = (char *)c.c_str();
+ std::cout << "Setting pg_autoscaler to 'off'" << std::endl;
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL,
+ 0, NULL, 0));
+
+ // disable scrubs for the test
+ c = string("{\"prefix\": \"osd set\",\"key\":\"noscrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+ c = string("{\"prefix\": \"osd set\",\"key\":\"nodeep-scrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+
+ RadosTest::SetUp();
+ }
+
+ void TearDown() override {
+ // re-enable pg autoscaler
+ string c =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"on\""
+ "}";
+ char *cmd[1];
+ cmd[0] = (char *)c.c_str();
+ std::cout << "Setting pg_autoscaler to 'on'" << std::endl;
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL,
+ 0, NULL, 0));
+
+ // re-enable scrubs
+ c = string("{\"prefix\": \"osd unset\",\"key\":\"noscrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+ c = string("{\"prefix\": \"osd unset\",\"key\":\"nodeep-scrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+
+ RadosTest::TearDown();
+ }
+};
+
+class LibRadosSnapshotStatsSelfManagedEC : public RadosTestEC {
+public:
+ LibRadosSnapshotStatsSelfManagedEC() {};
+ ~LibRadosSnapshotStatsSelfManagedEC() override {};
+protected:
+ void SetUp() override {
+ // disable pg autoscaler for the tests
+ string c =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"off\""
+ "}";
+ char *cmd[1];
+ cmd[0] = (char *)c.c_str();
+ std::cout << "Setting pg_autoscaler to 'off'" << std::endl;
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL,
+ 0, NULL, 0));
+
+ // disable scrubs for the test
+ c = string("{\"prefix\": \"osd set\",\"key\":\"noscrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+ c = string("{\"prefix\": \"osd set\",\"key\":\"nodeep-scrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+
+ RadosTestEC::SetUp();
+ }
+
+ void TearDown() override {
+ // re-enable pg autoscaler
+ string c =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"on\""
+ "}";
+ char *cmd[1];
+ cmd[0] = (char *)c.c_str();
+ std::cout << "Setting pg_autoscaler to 'on'" << std::endl;
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL,
+ 0, NULL, 0));
+
+ // re-enable scrubs
+ c = string("{\"prefix\": \"osd unset\",\"key\":\"noscrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+ c = string("{\"prefix\": \"osd unset\",\"key\":\"nodeep-scrub\"}");
+ cmd[0] = (char *)c.c_str();
+ ASSERT_EQ(0, rados_mon_command(s_cluster, (const char **)cmd, 1, "", 0, NULL, 0, NULL, 0));
+
+ RadosTestEC::TearDown();
+ }
+};
+
+int get_objects_trimmed(json_spirit::Object& pg_dump) {
+ int objs_trimmed = 0;
+
+ // pg_map
+ json_spirit::Object pgmap;
+ for (json_spirit::Object::size_type i = 0; i < pg_dump.size(); ++i) {
+ json_spirit::Pair& p = pg_dump[i];
+ if (p.name_ == "pg_map") {
+ pgmap = p.value_.get_obj();
+ break;
+ }
+ }
+
+ // pg_stats array
+ json_spirit::Array pgs;
+ for (json_spirit::Object::size_type i = 0; i < pgmap.size(); ++i) {
+ json_spirit::Pair& p = pgmap[i];
+ if (p.name_ == "pg_stats") {
+ pgs = p.value_.get_array();
+ break;
+ }
+ }
+
+ // snaptrim stats
+ for (json_spirit::Object::size_type j = 0; j < pgs.size(); ++j) {
+ json_spirit::Object& pg_stat = pgs[j].get_obj();
+ for(json_spirit::Object::size_type k = 0; k < pg_stat.size(); ++k) {
+ json_spirit::Pair& stats = pg_stat[k];
+ if (stats.name_ == "objects_trimmed") {
+ objs_trimmed += stats.value_.get_int();
+ break;
+ }
+ }
+ }
+
+ return objs_trimmed;
+}
+
+const int bufsize = 128;
+
+TEST_F(LibRadosSnapshotStatsSelfManaged, SnaptrimStats) {
+ int num_objs = 10;
+
+ // create objects
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, rados_write(ioctx, obj.c_str(), buf, sizeof(buf), 0));
+ }
+
+ std::vector<uint64_t> my_snaps;
+ for (int snap = 0; snap < 1; ++snap) {
+ // create a snapshot, clone
+ std::vector<uint64_t> ns(1);
+ ns.insert(ns.end(), my_snaps.begin(), my_snaps.end());
+ my_snaps.swap(ns);
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_create(ioctx, &my_snaps[0]));
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, rados_write(ioctx, obj.c_str(), buf2, sizeof(buf2), 0));
+ }
+ }
+
+ // wait for maps to settle
+ ASSERT_EQ(0, rados_wait_for_latest_osdmap(cluster));
+
+ // remove snaps - should trigger snaptrim
+ rados_ioctx_snap_set_read(ioctx, LIBRADOS_SNAP_HEAD);
+ for (unsigned snap = 0; snap < my_snaps.size(); ++snap) {
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_remove(ioctx, my_snaps[snap]));
+ }
+
+ // sleep for few secs for the trim stats to populate
+ std::cout << "Waiting for snaptrim stats to be generated" << std::endl;
+ sleep(30);
+
+ // Dump pg stats and determine if snaptrim stats are getting set
+ int objects_trimmed = 0;
+ int tries = 0;
+ do {
+ char *buf, *st;
+ size_t buflen, stlen;
+ string c = string("{\"prefix\": \"pg dump\",\"format\":\"json\"}");
+ const char *cmd = c.c_str();
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)&cmd, 1, "", 0,
+ &buf, &buflen, &st, &stlen));
+ string outstr(buf, buflen);
+ json_spirit::Value v;
+ ASSERT_NE(0, json_spirit::read(outstr, v)) << "unable to parse json."
+ << '\n' << outstr;
+
+ // pg dump object
+ json_spirit::Object& obj = v.get_obj();
+ objects_trimmed = get_objects_trimmed(obj);
+ if (objects_trimmed < num_objs) {
+ tries++;
+ objects_trimmed = 0;
+ std::cout << "Still waiting for all objects to be trimmed... " <<std::endl;
+ sleep(30);
+ }
+ } while(objects_trimmed < num_objs && tries < 5);
+
+ // final check for objects trimmed
+ ASSERT_EQ(objects_trimmed, num_objs);
+
+ // clean-up remaining objects
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, rados_remove(ioctx, obj.c_str()));
+ }
+}
+
+// EC testing
+TEST_F(LibRadosSnapshotStatsSelfManagedEC, SnaptrimStats) {
+ int num_objs = 10;
+ int bsize = alignment;
+ char *buf = (char *)new char[bsize];
+ memset(buf, 0xcc, bsize);
+ // create objects
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, rados_write(ioctx, obj.c_str(), buf, bsize, 0));
+ }
+
+ std::vector<uint64_t> my_snaps;
+ for (int snap = 0; snap < 1; ++snap) {
+ // create a snapshot, clone
+ std::vector<uint64_t> ns(1);
+ ns.insert(ns.end(), my_snaps.begin(), my_snaps.end());
+ my_snaps.swap(ns);
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_create(ioctx, &my_snaps[0]));
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_set_write_ctx(ioctx, my_snaps[0],
+ &my_snaps[0], my_snaps.size()));
+ char *buf2 = (char *)new char[bsize];
+ memset(buf2, 0xdd, bsize);
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, rados_write(ioctx, obj.c_str(), buf2, bsize, bsize));
+ }
+ delete[] buf2;
+ }
+
+ // wait for maps to settle
+ ASSERT_EQ(0, rados_wait_for_latest_osdmap(cluster));
+
+ // remove snaps - should trigger snaptrim
+ rados_ioctx_snap_set_read(ioctx, LIBRADOS_SNAP_HEAD);
+ for (unsigned snap = 0; snap < my_snaps.size(); ++snap) {
+ ASSERT_EQ(0, rados_ioctx_selfmanaged_snap_remove(ioctx, my_snaps[snap]));
+ }
+
+ // sleep for few secs for the trim stats to populate
+ std::cout << "Waiting for snaptrim stats to be generated" << std::endl;
+ sleep(30);
+
+ // Dump pg stats and determine if snaptrim stats are getting set
+ int objects_trimmed = 0;
+ int tries = 0;
+ do {
+ char *buf, *st;
+ size_t buflen, stlen;
+ string c = string("{\"prefix\": \"pg dump\",\"format\":\"json\"}");
+ const char *cmd = c.c_str();
+ ASSERT_EQ(0, rados_mon_command(cluster, (const char **)&cmd, 1, 0, 0,
+ &buf, &buflen, &st, &stlen));
+ string outstr(buf, buflen);
+ json_spirit::Value v;
+ ASSERT_NE(0, json_spirit::read(outstr, v)) << "Unable tp parse json."
+ << '\n' << outstr;
+
+ // pg dump object
+ json_spirit::Object& obj = v.get_obj();
+ objects_trimmed = get_objects_trimmed(obj);
+ if (objects_trimmed != num_objs) {
+ tries++;
+ objects_trimmed = 0;
+ std::cout << "Still waiting for all objects to be trimmed... " <<std::endl;
+ sleep(30);
+ }
+ } while (objects_trimmed != num_objs && tries < 5);
+
+ // final check for objects trimmed
+ ASSERT_EQ(objects_trimmed, num_objs);
+
+ // clean-up remaining objects
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, rados_remove(ioctx, obj.c_str()));
+ }
+
+ delete[] buf;
+}
--- /dev/null
+#include <algorithm>
+#include <errno.h>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#include "include/rados.h"
+#include "include/rados/librados.hpp"
+#include "json_spirit/json_spirit.h"
+#include "test/librados/test_cxx.h"
+#include "test/librados/testcase_cxx.h"
+
+using namespace librados;
+
+using std::string;
+
+class LibRadosSnapshotStatsSelfManagedPP : public RadosTestPP {
+public:
+ LibRadosSnapshotStatsSelfManagedPP() {};
+ ~LibRadosSnapshotStatsSelfManagedPP() override {};
+protected:
+ void SetUp() override {
+ // disable pg autoscaler for the tests
+ string cmd =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"off\""
+ "}";
+ std::cout << "Setting pg_autoscaler to 'off'" << std::endl;
+ bufferlist inbl;
+ bufferlist outbl;
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ // disable scrubs for the test
+ cmd = "{\"prefix\": \"osd set\",\"key\":\"noscrub\"}";
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+ cmd = "{\"prefix\": \"osd set\",\"key\":\"nodeep-scrub\"}";
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ RadosTestPP::SetUp();
+ }
+
+ void TearDown() override {
+ // re-enable pg autoscaler
+ string cmd =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"on\""
+ "}";
+ std::cout << "Setting pg_autoscaler to 'on'" << std::endl;
+ bufferlist inbl;
+ bufferlist outbl;
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ // re-enable scrubs
+ cmd = "{\"prefix\": \"osd unset\",\"key\":\"noscrub\"}";
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+ cmd = string("{\"prefix\": \"osd unset\",\"key\":\"nodeep-scrub\"}");
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ RadosTestPP::TearDown();
+ }
+};
+
+class LibRadosSnapshotStatsSelfManagedECPP : public RadosTestECPP {
+public:
+ LibRadosSnapshotStatsSelfManagedECPP() {};
+ ~LibRadosSnapshotStatsSelfManagedECPP() override {};
+protected:
+ void SetUp() override {
+ // disable pg autoscaler for the tests
+ string cmd =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"off\""
+ "}";
+ std::cout << "Setting pg_autoscaler to 'off'" << std::endl;
+ bufferlist inbl;
+ bufferlist outbl;
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ // disable scrubs for the test
+ cmd = string("{\"prefix\": \"osd set\",\"key\":\"noscrub\"}");
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+ cmd = string("{\"prefix\": \"osd set\",\"key\":\"nodeep-scrub\"}");
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ RadosTestECPP::SetUp();
+ }
+
+ void TearDown() override {
+ // re-enable pg autoscaler
+ string cmd =
+ "{"
+ "\"prefix\": \"config set\", "
+ "\"who\": \"global\", "
+ "\"name\": \"osd_pool_default_pg_autoscale_mode\", "
+ "\"value\": \"on\""
+ "}";
+ std::cout << "Setting pg_autoscaler to 'on'" << std::endl;
+ bufferlist inbl;
+ bufferlist outbl;
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ // re-enable scrubs
+ cmd = string("{\"prefix\": \"osd unset\",\"key\":\"noscrub\"}");
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+ cmd = string("{\"prefix\": \"osd unset\",\"key\":\"nodeep-scrub\"}");
+ ASSERT_EQ(0, s_cluster.mon_command(cmd, inbl, &outbl, NULL));
+
+ RadosTestECPP::TearDown();
+ }
+};
+
+int get_objects_trimmed(json_spirit::Object& pg_dump) {
+ int objs_trimmed = 0;
+
+ // pg_map
+ json_spirit::Object pgmap;
+ for (json_spirit::Object::size_type i = 0; i < pg_dump.size(); ++i) {
+ json_spirit::Pair& p = pg_dump[i];
+ if (p.name_ == "pg_map") {
+ pgmap = p.value_.get_obj();
+ break;
+ }
+ }
+
+ // pg_stats array
+ json_spirit::Array pgs;
+ for (json_spirit::Object::size_type i = 0; i < pgmap.size(); ++i) {
+ json_spirit::Pair& p = pgmap[i];
+ if (p.name_ == "pg_stats") {
+ pgs = p.value_.get_array();
+ break;
+ }
+ }
+
+ // snaptrim stats
+ for (json_spirit::Object::size_type j = 0; j < pgs.size(); ++j) {
+ json_spirit::Object& pg_stat = pgs[j].get_obj();
+ for(json_spirit::Object::size_type k = 0; k < pg_stat.size(); ++k) {
+ json_spirit::Pair& stats = pg_stat[k];
+ if (stats.name_ == "objects_trimmed") {
+ objs_trimmed += stats.value_.get_int();
+ break;
+ }
+ }
+ }
+
+ return objs_trimmed;
+}
+const int bufsize = 128;
+
+TEST_F(LibRadosSnapshotStatsSelfManagedPP, SnaptrimStatsPP) {
+ int num_objs = 10;
+
+ // create objects
+ char buf[bufsize];
+ memset(buf, 0xcc, sizeof(buf));
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, ioctx.write(obj, bl, sizeof(buf), 0));
+ }
+
+ std::vector<uint64_t> my_snaps;
+ char buf2[sizeof(buf)];
+ memset(buf2, 0xdd, sizeof(buf2));
+ bufferlist bl2;
+ bl2.append(buf2, sizeof(buf2));
+ for (int snap = 0; snap < 1; ++snap) {
+ // create a snapshot, clone
+ std::vector<uint64_t> ns(1);
+ ns.insert(ns.end(), my_snaps.begin(), my_snaps.end());
+ my_snaps.swap(ns);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, ioctx.write(obj, bl2, sizeof(buf2), 0));
+ }
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // remove snaps - should trigger snaptrim
+ for (unsigned snap = 0; snap < my_snaps.size(); ++snap) {
+ ioctx.selfmanaged_snap_remove(my_snaps[snap]);
+ }
+
+ // sleep for few secs for the trim stats to populate
+ std::cout << "Waiting for snaptrim stats to be generated" << std::endl;
+ sleep(30);
+
+ // Dump pg stats and determine if snaptrim stats are getting set
+ int objects_trimmed = 0;
+ int tries = 0;
+ do {
+ string cmd = string("{\"prefix\": \"pg dump\",\"format\":\"json\"}");
+ bufferlist inbl;
+ bufferlist outbl;
+ ASSERT_EQ(0, cluster.mon_command(cmd, inbl, &outbl, NULL));
+ string outstr(outbl.c_str(), outbl.length());
+ json_spirit::Value v;
+ ASSERT_NE(0, json_spirit::read(outstr, v)) << "unable to parse json." << '\n' << outstr;
+
+ // pg_map
+ json_spirit::Object& obj = v.get_obj();
+ objects_trimmed = get_objects_trimmed(obj);
+ if (objects_trimmed < num_objs) {
+ tries++;
+ objects_trimmed = 0;
+ std::cout << "Still waiting for all objects to be trimmed... " <<std::endl;
+ sleep(30);
+ }
+ } while(objects_trimmed < num_objs && tries < 5);
+
+ // final check for objects trimmed
+ ASSERT_EQ(objects_trimmed, num_objs);
+
+ // clean-up remaining objects
+ ioctx.snap_set_read(librados::SNAP_HEAD);
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, ioctx.remove(obj));
+ }
+}
+
+// EC testing
+TEST_F(LibRadosSnapshotStatsSelfManagedECPP, SnaptrimStatsECPP) {
+ int num_objs = 10;
+ int bsize = alignment;
+
+ // create objects
+ char *buf = (char *)new char[bsize];
+ memset(buf, 0xcc, bsize);
+ bufferlist bl;
+ bl.append(buf, bsize);
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, ioctx.write(obj, bl, bsize, 0));
+ }
+
+ std::vector<uint64_t> my_snaps;
+ char *buf2 = (char *)new char[bsize];
+ memset(buf2, 0xdd, bsize);
+ bufferlist bl2;
+ bl2.append(buf2, bsize);
+ for (int snap = 0; snap < 1; ++snap) {
+ // create a snapshot, clone
+ std::vector<uint64_t> ns(1);
+ ns.insert(ns.end(), my_snaps.begin(), my_snaps.end());
+ my_snaps.swap(ns);
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps[0]));
+ ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps));
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, ioctx.write(obj, bl2, bsize, bsize));
+ }
+ }
+
+ // wait for maps to settle
+ cluster.wait_for_latest_osdmap();
+
+ // remove snaps - should trigger snaptrim
+ for (unsigned snap = 0; snap < my_snaps.size(); ++snap) {
+ ioctx.selfmanaged_snap_remove(my_snaps[snap]);
+ }
+
+ // sleep for few secs for the trim stats to populate
+ std::cout << "Waiting for snaptrim stats to be generated" << std::endl;
+ sleep(30);
+
+ // Dump pg stats and determine if snaptrim stats are getting set
+ int objects_trimmed = 0;
+ int tries = 0;
+ do {
+ string cmd = string("{\"prefix\": \"pg dump\",\"format\":\"json\"}");
+ bufferlist inbl;
+ bufferlist outbl;
+ ASSERT_EQ(0, cluster.mon_command(cmd, inbl, &outbl, NULL));
+ string outstr(outbl.c_str(), outbl.length());
+ json_spirit::Value v;
+ ASSERT_NE(0, json_spirit::read(outstr, v)) << "unable to parse json." << '\n' << outstr;
+
+ // pg_map
+ json_spirit::Object& obj = v.get_obj();
+ objects_trimmed = get_objects_trimmed(obj);
+ if (objects_trimmed < num_objs) {
+ tries++;
+ objects_trimmed = 0;
+ std::cout << "Still waiting for all objects to be trimmed... " <<std::endl;
+ sleep(30);
+ }
+ } while(objects_trimmed < num_objs && tries < 5);
+
+ // final check for objects trimmed
+ ASSERT_EQ(objects_trimmed, num_objs);
+
+ // clean-up remaining objects
+ ioctx.snap_set_read(LIBRADOS_SNAP_HEAD);
+ for (int i = 0; i < num_objs; ++i) {
+ string obj = string("foo") + std::to_string(i);
+ ASSERT_EQ(0, ioctx.remove(obj));
+ }
+
+ delete[] buf;
+ delete[] buf2;
+}