diff --git a/src/control/cmd/ddb/commands_wrapper.go b/src/control/cmd/ddb/commands_wrapper.go index b0f6b380f4e..e393b7b7b47 100644 --- a/src/control/cmd/ddb/commands_wrapper.go +++ b/src/control/cmd/ddb/commands_wrapper.go @@ -9,10 +9,12 @@ package main import ( + "math" "runtime" "unsafe" "github.com/daos-stack/daos/src/control/lib/daos" + "github.com/daos-stack/daos/src/control/logging" ) /* @@ -36,7 +38,7 @@ func freeString(s *C.char) { } // InitDdb initializes the ddb context and returns a closure to finalize it. -func InitDdb() (*DdbContext, func(), error) { +func InitDdb(log *logging.LeveledLogger) (*DdbContext, func(), error) { // Must lock to OS thread because vos init/fini uses ABT init and finalize which must be called on the same thread runtime.LockOSThread() @@ -47,6 +49,7 @@ func InitDdb() (*DdbContext, func(), error) { ctx := &DdbContext{} C.ddb_ctx_init(&ctx.ctx) // Initialize with ctx default values + ctx.log = log return ctx, func() { C.ddb_fini() @@ -57,6 +60,7 @@ func InitDdb() (*DdbContext, func(), error) { // DdbContext structure for wrapping the C code context structure type DdbContext struct { ctx C.struct_ddb_ctx + log *logging.LeveledLogger } func ddbPoolIsOpen(ctx *DdbContext) bool { @@ -296,10 +300,11 @@ func ddbDevReplace(ctx *DdbContext, db_path string, old_devid string, new_devid return daosError(C.ddb_run_dev_replace(&ctx.ctx, &options)) } -func ddbDtxStat(ctx *DdbContext, path string) error { +func ddbDtxStat(ctx *DdbContext, path string, details bool) error { /* Set up the options */ options := C.struct_dtx_stat_options{} options.path = C.CString(path) + options.details = C.bool(details) defer freeString(options.path) /* Run the c code command */ return daosError(C.ddb_run_dtx_stat(&ctx.ctx, &options)) @@ -317,3 +322,30 @@ func ddbProvMem(ctx *DdbContext, db_path string, tmpfs_mount string, tmpfs_mount /* Run the c code command */ return daosError(C.ddb_run_prov_mem(&ctx.ctx, &options)) } + +func ddbDtxAggr(ctx *DdbContext, path string, cmt_time uint64, cmt_date string) error { + if cmt_time != math.MaxUint64 && cmt_date != "" { + ctx.log.Error("'--cmt_time' and '--cmt_date' options are mutually exclusive") + return daosError(-C.DER_INVAL) + } + if cmt_time == math.MaxUint64 && cmt_date == "" { + ctx.log.Error("'--cmt_time' or '--cmt_date' option has to be defined") + return daosError(-C.DER_INVAL) + } + + /* Set up the options */ + options := C.struct_dtx_aggr_options{} + options.path = C.CString(path) + defer freeString(options.path) + if cmt_time != math.MaxUint64 { + options.format = C.DDB_DTX_AGGR_CMT_TIME + options.cmt_time = C.uint64_t(cmt_time) + } + if cmt_date != "" { + options.format = C.DDB_DTX_AGGR_CMT_DATE + options.cmt_date = C.CString(cmt_date) + defer freeString(options.cmt_date) + } + /* Run the c code command */ + return daosError(C.ddb_run_dtx_aggr(&ctx.ctx, &options)) +} diff --git a/src/control/cmd/ddb/ddb_commands.go b/src/control/cmd/ddb/ddb_commands.go index de644d46085..bee8aeddc4d 100644 --- a/src/control/cmd/ddb/ddb_commands.go +++ b/src/control/cmd/ddb/ddb_commands.go @@ -9,6 +9,8 @@ package main import ( + "math" + "github.com/desertbit/grumble" ) @@ -391,11 +393,14 @@ the path must include the extent, otherwise, it must not.`, Help: "Stat on DTX entries", LongHelp: "Print statistic on the DTX entries", HelpGroup: "vos", + Flags: func(a *grumble.Flags) { + a.Bool("d", "details", false, "Show detailed time related stats.") + }, Args: func(a *grumble.Args) { - a.String("path", "Optional, VOS tree path to query.", grumble.Default("")) + a.String("path", "Optional, VOS tree path of a container to query.", grumble.Default("")) }, Run: func(c *grumble.Context) error { - return ddbDtxStat(ctx, c.Args.String("path")) + return ddbDtxStat(ctx, c.Args.String("path"), c.Flags.Bool("details")) }, Completer: nil, }) @@ -418,4 +423,23 @@ the path must include the extent, otherwise, it must not.`, }, Completer: nil, }) + // Command dtx_aggr + app.AddCommand(&grumble.Command{ + Name: "dtx_aggr", + Aliases: nil, + Help: "Aggregate DTX entries", + LongHelp: "Aggregate DTX entries until a given aggregation commit time or date", + HelpGroup: "vos", + Args: func(a *grumble.Args) { + a.String("path", "Optional, VOS tree path of a container to aggregate.", grumble.Default("")) + }, + Flags: func(f *grumble.Flags) { + f.Uint64("t", "cmt_time", math.MaxUint64, "Max aggregfation committed time in seconds") + f.String("d", "cmt_date", "", "Max aggregation committed date (format '1970-01-01 00:00:00')") + }, + Run: func(c *grumble.Context) error { + return ddbDtxAggr(ctx, c.Args.String("path"), c.Flags.Uint64("cmt_time"), c.Flags.String("cmt_date")) + }, + Completer: nil, + }) } diff --git a/src/control/cmd/ddb/main.go b/src/control/cmd/ddb/main.go index 5d86ad04453..41fa0b6021f 100644 --- a/src/control/cmd/ddb/main.go +++ b/src/control/cmd/ddb/main.go @@ -71,7 +71,7 @@ type ddbCmdStr string func (cmdStr ddbCmdStr) Complete(match string) (comps []flags.Completion) { // hack to get at command names - ctx, cleanup, err := InitDdb() + ctx, cleanup, err := InitDdb(nil) if err != nil { return } @@ -176,7 +176,7 @@ Example Paths: log.Debug("debug output enabled") } - ctx, cleanup, err := InitDdb() + ctx, cleanup, err := InitDdb(log) if err != nil { return errors.Wrap(err, "Error initializing the DDB Context") } diff --git a/src/dtx/dtx_common.c b/src/dtx/dtx_common.c index 0c94d3cc713..175f440dc4e 100644 --- a/src/dtx/dtx_common.c +++ b/src/dtx/dtx_common.c @@ -430,8 +430,8 @@ dtx_aggregate(void *arg) struct dtx_stat stat = { 0 }; int rc; - rc = vos_dtx_aggregate(cont->sc_hdl); - if (rc != 0) + rc = vos_dtx_aggregate(cont->sc_hdl, NULL); + if (rc < 0) break; ABT_thread_yield(); diff --git a/src/dtx/tests/SConscript b/src/dtx/tests/SConscript index 1a97b9bdff0..2ea2e93eec5 100644 --- a/src/dtx/tests/SConscript +++ b/src/dtx/tests/SConscript @@ -51,6 +51,10 @@ def scons(): tenv.AppendUnique(LINKFLAGS=['-Wl,--wrap=vos_irec_is_valid']) tenv.AppendUnique(LINKFLAGS=['-Wl,--wrap=evt_desc_is_valid']) tenv.AppendUnique(LINKFLAGS=['-Wl,--wrap=dbtree_lookup']) + tenv.AppendUnique(LINKFLAGS=['-Wl,--wrap=vos_tls_get']) + tenv.AppendUnique(LINKFLAGS=['-Wl,--wrap=lrua_array_aggregate']) + tenv.AppendUnique(LINKFLAGS=['-Wl,--wrap=dbtree_delete']) + tenv.AppendUnique(LINKFLAGS=['-Wl,--wrap=d_tm_dec_gauge']) vos_src = Glob('../../vos/*.c') @@ -61,7 +65,8 @@ def scons(): 'sched_mock.c', 'srv_mock.c', 'srv_module_mock.c', - 'dts_count.c' + 'dts_cmt_stat.c', + 'dts_aggregate.c' ] dtx_ut = tenv.d_program('dtx_ut', test_src + vos_src, LIBS=libraries) diff --git a/src/dtx/tests/dts_aggregate.c b/src/dtx/tests/dts_aggregate.c new file mode 100644 index 00000000000..0f341cf9f5b --- /dev/null +++ b/src/dtx/tests/dts_aggregate.c @@ -0,0 +1,812 @@ +/** + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP. + * + * SPDX-License-Identifier: BSD-2-Clause-Patent + */ +#define D_LOGFAC DD_FAC(tests) + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "ilog.h" +#include "vos_layout.h" +#include "vos_internal.h" + +/* mocks */ + +#define DTX_CMT_BLOB_MAGIC 0x2502191c +#define DBD_BLOBS_CAP 0x3 +#define DBD_BLOB_DF_CAP 0x8 +#define CMT_TIME_START 0x1000 +#define CMT_TIME_END 0x10000 +#define CMT_TIME_STEP 0x10 +#define EPOCH_OFFSET 0x20000 +#define MOCK_UMM_TX_MAX 0x100 +#define CELL_SIZE \ + sizeof(struct vos_dtx_blob_df) + DBD_BLOB_DF_CAP * sizeof(struct vos_dtx_cmt_ent_df) + +struct mocked_umm_tx { + bool in_tx; + size_t count; + struct { + void *target; + void *snapshot; + size_t size; + } tx[MOCK_UMM_TX_MAX]; +}; + +static struct vos_pool mock_pool; +static struct vos_container mock_cont; +static struct vos_cont_df mock_cont_df; +static struct vos_tls mock_tls; +static struct vos_dtx_blob_df *mock_dbds[DBD_BLOBS_CAP]; +static umem_off_t mock_dbds_off[DBD_BLOBS_CAP]; +static struct btr_instance mock_btr; +static daos_handle_t mock_coh; +static struct mocked_umm_tx mock_umm_tx; + +struct vos_tls * +__wrap_vos_tls_get(bool standalone) +{ + return mock_ptr_type(struct vos_tls *); +} + +void +__wrap_lrua_array_aggregate(struct lru_array *array) +{ + /* nop */ + return; +} + +int +__wrap_dbtree_delete(daos_handle_t toh, dbtree_probe_opc_t opc, d_iov_t *key, void *args) +{ + assert_int_equal(toh.cookie, mock_cont.vc_dtx_committed_hdl.cookie); + assert_int_equal(opc, BTR_PROBE_EQ); + assert_non_null(key); + assert_null(args); + return mock(); +} + +void +__wrap_d_tm_dec_gauge(struct d_tm_node_t *metric, uint64_t value) +{ + check_expected_ptr(metric); + check_expected(value); +} + +static int +tx_begin(struct umem_instance *umm, struct umem_tx_stage_data *txd) +{ + int rc; + + assert_ptr_equal(umm, &mock_pool.vp_umm); + assert_null(txd); + assert_int_equal(mock_umm_tx.count, 0); + rc = mock(); + mock_umm_tx.in_tx = (rc == 0); + return rc; +} + +static int +tx_commit(struct umem_instance *umm, void *data) +{ + int i; + int rc; + + assert_ptr_equal(umm, &mock_pool.vp_umm); + assert_null(data); + assert_true(mock_umm_tx.in_tx); + rc = mock(); + for (i = 0; i < mock_umm_tx.count; i++) { + if (rc != 0) + memcpy(mock_umm_tx.tx[i].target, mock_umm_tx.tx[i].snapshot, + mock_umm_tx.tx[i].size); + D_FREE(mock_umm_tx.tx[i].snapshot); + } + mock_umm_tx.count = 0; + mock_umm_tx.in_tx = false; + return rc; +} + +static int +tx_abort(struct umem_instance *umm, int error) +{ + int i; + + assert_ptr_equal(umm, &mock_pool.vp_umm); + check_expected(error); + assert_true(mock_umm_tx.in_tx); + for (i = 0; i < mock_umm_tx.count; i++) { + memcpy(mock_umm_tx.tx[i].target, mock_umm_tx.tx[i].snapshot, + mock_umm_tx.tx[i].size); + D_FREE(mock_umm_tx.tx[i].snapshot); + } + mock_umm_tx.count = 0; + mock_umm_tx.in_tx = false; + if (error) { + return error; + } + return mock(); +} + +static int +tx_add_ptr(struct umem_instance *umm, void *ptr, size_t ptr_size) +{ + assert_ptr_equal(umm, &mock_pool.vp_umm); + check_expected_ptr(ptr); + check_expected(ptr_size); + D_ALLOC(mock_umm_tx.tx[mock_umm_tx.count].snapshot, ptr_size); + assert_non_null(mock_umm_tx.tx[mock_umm_tx.count].snapshot); + memcpy(mock_umm_tx.tx[mock_umm_tx.count].snapshot, ptr, ptr_size); + mock_umm_tx.tx[mock_umm_tx.count].target = ptr; + mock_umm_tx.tx[mock_umm_tx.count].size = ptr_size; + mock_umm_tx.count++; + return mock(); +} + +static int +tx_free(struct umem_instance *umm, umem_off_t umoff) +{ + assert_ptr_equal(umm, &mock_pool.vp_umm); + check_expected(umoff); + assert_true(mock_umm_tx.in_tx); + return mock(); +} + +/* Helpers */ + +static void +prep_dtx_entries(void) +{ + int i; + uint64_t cmt_time; + + cmt_time = CMT_TIME_START; + for (i = 0; i < DBD_BLOBS_CAP; i++) { + int j; + + for (j = 0; j < DBD_BLOB_DF_CAP; j++) { + struct vos_dtx_cmt_ent_df *dce_df; + + dce_df = &mock_dbds[i]->dbd_committed_data[j]; + dce_df->dce_cmt_time = cmt_time; + dce_df->dce_epoch = cmt_time + EPOCH_OFFSET; + cmt_time += CMT_TIME_STEP; + } + mock_dbds[i]->dbd_count = DBD_BLOB_DF_CAP; + } + mock_cont.vc_dtx_committed_count = DBD_BLOBS_CAP * DBD_BLOB_DF_CAP; + mock_pool.vp_dtx_committed_count = DBD_BLOBS_CAP * DBD_BLOB_DF_CAP; +} + +static void +check_rollback(void) +{ + int i; + uint64_t cmt_time; + + cmt_time = CMT_TIME_START; + for (i = 0; i < DBD_BLOBS_CAP; i++) { + int j; + + for (j = 0; j < DBD_BLOB_DF_CAP; j++) { + assert_int_equal(mock_dbds[i]->dbd_committed_data[j].dce_cmt_time, + cmt_time); + assert_int_equal(mock_dbds[i]->dbd_committed_data[j].dce_epoch, + cmt_time + EPOCH_OFFSET); + cmt_time += CMT_TIME_STEP; + } + assert_int_equal(mock_dbds[i]->dbd_count, DBD_BLOB_DF_CAP); + } + assert_int_equal(mock_cont_df.cd_newest_aggregated, 0); + assert_int_equal(mock_cont_df.cd_dtx_committed_head, + umem_ptr2off(&mock_pool.vp_umm, mock_dbds[0])); + assert_int_equal(mock_cont_df.cd_dtx_committed_tail, + umem_ptr2off(&mock_pool.vp_umm, mock_dbds[DBD_BLOBS_CAP - 1])); + assert_int_equal(mock_cont.vc_dtx_committed_count, DBD_BLOBS_CAP * DBD_BLOB_DF_CAP); + assert_int_equal(mock_pool.vp_dtx_committed_count, DBD_BLOBS_CAP * DBD_BLOB_DF_CAP); + assert_int_equal(mock_cont.vc_cmt_dtx_reindex_pos, + umem_ptr2off(&mock_pool.vp_umm, mock_dbds[0])); +} + +/* setup & teardown */ + +static umem_ops_t umm_ops = {.mo_tx_begin = tx_begin, + .mo_tx_commit = tx_commit, + .mo_tx_abort = tx_abort, + .mo_tx_add_ptr = tx_add_ptr, + .mo_tx_free = tx_free}; + +static int +test_setup(void **unused) +{ + int i; + + memset(&mock_pool, 0, sizeof(mock_pool)); + memset(&mock_cont, 0, sizeof(mock_cont)); + memset(&mock_cont_df, 0, sizeof(mock_cont_df)); + memset(&mock_tls, 0, sizeof(mock_tls)); + memset(&mock_umm_tx, 0, sizeof(mock_umm_tx)); + + for (i = 0; i < DBD_BLOBS_CAP; i++) { + D_ALLOC(mock_dbds[i], CELL_SIZE); + assert_non_null(mock_dbds[i]); + mock_dbds_off[i] = umem_ptr2off(&mock_pool.vp_umm, mock_dbds[i]); + } + + for (i = 0; i < DBD_BLOBS_CAP; i++) { + mock_dbds[i]->dbd_magic = DTX_CMT_BLOB_MAGIC; + mock_dbds[i]->dbd_cap = DBD_BLOB_DF_CAP; + mock_dbds[i]->dbd_next = + (i == DBD_BLOBS_CAP - 1) ? UMOFF_NULL : mock_dbds_off[i + 1]; + mock_dbds[i]->dbd_prev = (i == 0) ? UMOFF_NULL : mock_dbds_off[i - 1]; + mock_dbds[i]->dbd_count = 0; + assert_true(mock_dbds[i]->dbd_count <= DBD_BLOB_DF_CAP); + } + + mock_pool.vp_umm.umm_ops = &umm_ops; + mock_cont.vc_pool = &mock_pool; + mock_cont.vc_cont_df = &mock_cont_df; + mock_cont.vc_cmt_dtx_reindex_pos = mock_dbds_off[0]; + mock_cont.vc_dtx_committed_hdl.cookie = (uint64_t)&mock_btr; + mock_cont_df.cd_dtx_committed_head = mock_dbds_off[0]; + mock_cont_df.cd_dtx_committed_tail = mock_dbds_off[DBD_BLOBS_CAP - 1]; + mock_coh.cookie = (uint64_t)&mock_cont; + + return 0; +} + +static int +test_empty_setup(void **unused) +{ + int i; + + memset(&mock_pool, 0, sizeof(mock_pool)); + memset(&mock_cont, 0, sizeof(mock_cont)); + memset(&mock_cont_df, 0, sizeof(mock_cont_df)); + memset(&mock_tls, 0, sizeof(mock_tls)); + memset(&mock_umm_tx, 0, sizeof(mock_umm_tx)); + + for (i = 0; i < DBD_BLOBS_CAP; i++) { + mock_dbds[i] = NULL; + mock_dbds_off[i] = umem_ptr2off(&mock_pool.vp_umm, mock_dbds[i]); + } + mock_pool.vp_umm.umm_ops = &umm_ops; + mock_cont.vc_pool = &mock_pool; + mock_cont.vc_cont_df = &mock_cont_df; + mock_cont.vc_cmt_dtx_reindex_pos = UMOFF_NULL; + mock_cont.vc_dtx_committed_hdl.cookie = (uint64_t)&mock_btr; + mock_cont_df.cd_dtx_committed_head = UMOFF_NULL; + mock_cont_df.cd_dtx_committed_tail = UMOFF_NULL; + mock_coh.cookie = (uint64_t)&mock_cont; + + return 0; +} + +static int +test_teardown(void **unused) +{ + int i; + + assert_int_equal(mock_umm_tx.count, 0); + for (i = 0; i < DBD_BLOBS_CAP; i++) + D_FREE(mock_dbds[i]); + + return 0; +} + +static int +test_empty_teardown(void **unused) +{ + assert_int_equal(mock_umm_tx.count, 0); + + return 0; +} + +/* tests */ + +static void +test_asserts(void **unused) +{ + daos_handle_t hdl_null = {0}; + + /* Invalid Telemetry global variable. */ + will_return(__wrap_vos_tls_get, NULL); + expect_assert_failure(vos_dtx_aggregate(mock_coh, NULL)); + + /* Missing argument. */ + will_return(__wrap_vos_tls_get, NULL); + expect_assert_failure(vos_dtx_aggregate(hdl_null, NULL)); + + /* Invalid pool type. */ + will_return(__wrap_vos_tls_get, &mock_tls); + mock_pool.vp_sysdb = true; + expect_assert_failure(vos_dtx_aggregate(mock_coh, NULL)); +} + +/* Can not start a PMEM transtation */ +static void +test_tx_begin_error(void **unused) +{ + int rc; + + prep_dtx_entries(); + + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, -DER_UNKNOWN); + + rc = vos_dtx_aggregate(mock_coh, NULL); + assert_rc_equal(rc, -DER_UNKNOWN); + check_rollback(); +} + +/* DAOS B-tree delete failure */ +static void +test_dbtree_delete_error(void **unused) +{ + int i; + int rc; + + prep_dtx_entries(); + + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, 0); + for (i = 0; i < 3; i++) + will_return(__wrap_dbtree_delete, 0); + will_return(__wrap_dbtree_delete, -DER_UNKNOWN); + expect_value(tx_abort, error, -DER_UNKNOWN); + + rc = vos_dtx_aggregate(mock_coh, NULL); + assert_rc_equal(rc, -DER_UNKNOWN); + check_rollback(); +} + +/* Update of newest aggregated epoch failure */ +static void +test_newest_aggregated_error(void **unused) +{ + int i; + int rc; + + prep_dtx_entries(); + + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, 0); + for (i = 0; i < DBD_BLOB_DF_CAP; i++) + will_return(__wrap_dbtree_delete, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_newest_aggregated); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_cont_df.cd_newest_aggregated)); + will_return(tx_add_ptr, -DER_UNKNOWN); + expect_value(tx_abort, error, -DER_UNKNOWN); + + rc = vos_dtx_aggregate(mock_coh, NULL); + assert_rc_equal(rc, -DER_UNKNOWN); + check_rollback(); +} + +/* Update of DTX blob list failure */ +static void +test_committed_head_error(void **unused) +{ + int i; + int rc; + + prep_dtx_entries(); + + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, 0); + for (i = 0; i < DBD_BLOB_DF_CAP; i++) + will_return(__wrap_dbtree_delete, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_newest_aggregated); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_cont_df.cd_newest_aggregated)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_dtx_committed_head); + expect_value(tx_add_ptr, ptr_size, sizeof(umem_off_t)); + will_return(tx_add_ptr, -DER_UNKNOWN); + expect_value(tx_abort, error, -DER_UNKNOWN); + + rc = vos_dtx_aggregate(mock_coh, NULL); + assert_rc_equal(rc, -DER_UNKNOWN); + check_rollback(); +} + +/* Update of DTX blob list failure */ +static void +test_committed_prev_error(void **unused) +{ + int i; + int rc; + + prep_dtx_entries(); + + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, 0); + for (i = 0; i < DBD_BLOB_DF_CAP; i++) + will_return(__wrap_dbtree_delete, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_newest_aggregated); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_cont_df.cd_newest_aggregated)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_dtx_committed_head); + expect_value(tx_add_ptr, ptr_size, sizeof(umem_off_t)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_dbds[1]->dbd_prev); + expect_value(tx_add_ptr, ptr_size, sizeof(umem_off_t)); + will_return(tx_add_ptr, -DER_UNKNOWN); + expect_value(tx_abort, error, -DER_UNKNOWN); + + rc = vos_dtx_aggregate(mock_coh, NULL); + assert_rc_equal(rc, -DER_UNKNOWN); + check_rollback(); +} + +/* Pmem free failure */ +static void +test_umm_free_error(void **unused) +{ + int i; + int rc; + + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, 0); + prep_dtx_entries(); + for (i = 0; i < DBD_BLOB_DF_CAP; i++) + will_return(__wrap_dbtree_delete, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_newest_aggregated); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_cont_df.cd_newest_aggregated)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_dtx_committed_head); + expect_value(tx_add_ptr, ptr_size, sizeof(umem_off_t)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_dbds[1]->dbd_prev); + expect_value(tx_add_ptr, ptr_size, sizeof(umem_off_t)); + will_return(tx_add_ptr, 0); + + expect_value(tx_free, umoff, mock_dbds_off[0]); + will_return(tx_free, -DER_UNKNOWN); + expect_value(tx_abort, error, -DER_UNKNOWN); + + rc = vos_dtx_aggregate(mock_coh, NULL); + assert_rc_equal(rc, -DER_UNKNOWN); + check_rollback(); +} + +/* Update of committed DTX entries failure */ +static void +test_committed_data_error(void **unused) +{ + uint64_t cmt_time; + int i; + int rc; + const int dtx_count = 3; + + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, 0); + prep_dtx_entries(); + for (i = 0; i < dtx_count; i++) + will_return(__wrap_dbtree_delete, -DER_NONEXIST); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_newest_aggregated); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_cont_df.cd_newest_aggregated)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_dbds[0]->dbd_committed_data[0]); + expect_value(tx_add_ptr, ptr_size, + sizeof(mock_dbds[0]->dbd_committed_data[0]) * mock_dbds[0]->dbd_count); + will_return(tx_add_ptr, -DER_UNKNOWN); + expect_value(tx_abort, error, -DER_UNKNOWN); + + cmt_time = CMT_TIME_START + (dtx_count - 1) * CMT_TIME_STEP; + rc = vos_dtx_aggregate(mock_coh, &cmt_time); + assert_rc_equal(rc, -DER_UNKNOWN); + check_rollback(); +} + +/* Update of committed DTX entries count failure */ +static void +test_dbd_count_error(void **unused) +{ + uint64_t cmt_time; + int i; + int rc; + const int dtx_count = 3; + + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, 0); + prep_dtx_entries(); + for (i = 0; i < dtx_count; i++) + will_return(__wrap_dbtree_delete, -DER_NONEXIST); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_newest_aggregated); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_cont_df.cd_newest_aggregated)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_dbds[0]->dbd_committed_data[0]); + expect_value(tx_add_ptr, ptr_size, + sizeof(mock_dbds[0]->dbd_committed_data[0]) * mock_dbds[0]->dbd_count); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_dbds[0]->dbd_count); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_dbds[0]->dbd_count)); + will_return(tx_add_ptr, -DER_UNKNOWN); + expect_value(tx_abort, error, -DER_UNKNOWN); + + cmt_time = CMT_TIME_START + (dtx_count - 1) * CMT_TIME_STEP; + rc = vos_dtx_aggregate(mock_coh, &cmt_time); + assert_rc_equal(rc, -DER_UNKNOWN); + check_rollback(); +} + +/* Pmem commit transaction failure */ +static void +test_umm_commit_error(void **unused) +{ + uint64_t cmt_time; + int i; + int rc; + const int dtx_count = 3; + + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, 0); + prep_dtx_entries(); + for (i = 0; i < dtx_count; i++) + will_return(__wrap_dbtree_delete, -DER_NONEXIST); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_newest_aggregated); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_cont_df.cd_newest_aggregated)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_dbds[0]->dbd_committed_data[0]); + expect_value(tx_add_ptr, ptr_size, + sizeof(mock_dbds[0]->dbd_committed_data[0]) * mock_dbds[0]->dbd_count); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_dbds[0]->dbd_count); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_dbds[0]->dbd_count)); + will_return(tx_add_ptr, 0); + will_return(tx_commit, -DER_UNKNOWN); + + cmt_time = CMT_TIME_START + (dtx_count - 1) * CMT_TIME_STEP; + rc = vos_dtx_aggregate(mock_coh, &cmt_time); + assert_rc_equal(rc, -DER_UNKNOWN); + check_rollback(); +} + +/* Pool without DTX committed transaction */ +static void +test_empty(void **unused) +{ + int rc; + + will_return(__wrap_vos_tls_get, &mock_tls); + + rc = vos_dtx_aggregate(mock_coh, NULL); + assert_rc_equal(rc, 0); + assert_int_equal(mock_cont_df.cd_newest_aggregated, 0); + assert_int_equal(mock_cont_df.cd_dtx_committed_head, UMOFF_NULL); + assert_int_equal(mock_cont_df.cd_dtx_committed_tail, UMOFF_NULL); + assert_int_equal(mock_cont.vc_dtx_committed_count, 0); + assert_int_equal(mock_pool.vp_dtx_committed_count, 0); + assert_int_equal(mock_cont.vc_cmt_dtx_reindex_pos, UMOFF_NULL); +} + +/* Pool with empty DTX entries blob*/ +static void +test_empty_blob(void **unused) +{ + int i; + int rc; + + for (i = 0; i < DBD_BLOBS_CAP; i++) { + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_dtx_committed_head); + expect_value(tx_add_ptr, ptr_size, sizeof(umem_off_t)); + will_return(tx_add_ptr, 0); + if (i == DBD_BLOBS_CAP - 1) + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_dtx_committed_tail); + else + expect_value(tx_add_ptr, ptr, &mock_dbds[i + 1]->dbd_prev); + expect_value(tx_add_ptr, ptr_size, sizeof(umem_off_t)); + will_return(tx_add_ptr, 0); + expect_value(tx_free, umoff, mock_dbds_off[i]); + will_return(tx_free, 0); + will_return(tx_commit, 0); + } + + do { + rc = vos_dtx_aggregate(mock_coh, NULL); + assert_true(rc >= 0); + } while (rc > 0); + assert_int_equal(mock_cont_df.cd_newest_aggregated, 0); + assert_int_equal(mock_cont_df.cd_dtx_committed_head, UMOFF_NULL); + assert_int_equal(mock_cont_df.cd_dtx_committed_tail, UMOFF_NULL); + assert_int_equal(mock_cont.vc_dtx_committed_count, 0); + assert_int_equal(mock_pool.vp_dtx_committed_count, 0); + assert_int_equal(mock_cont.vc_cmt_dtx_reindex_pos, UMOFF_NULL); +} + +/* Aggregation of all the committed DTX transactions of one blob */ +static void +test_one_blob(void **unused) +{ + int i; + int rc; + + prep_dtx_entries(); + + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, 0); + for (i = 0; i < DBD_BLOB_DF_CAP; i++) + will_return(__wrap_dbtree_delete, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_newest_aggregated); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_cont_df.cd_newest_aggregated)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_dtx_committed_head); + expect_value(tx_add_ptr, ptr_size, sizeof(umem_off_t)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_dbds[1]->dbd_prev); + expect_value(tx_add_ptr, ptr_size, sizeof(umem_off_t)); + will_return(tx_add_ptr, 0); + expect_value(tx_free, umoff, mock_dbds_off[0]); + will_return(tx_free, 0); + will_return(tx_commit, 0); + expect_value(__wrap_d_tm_dec_gauge, metric, mock_tls.vtl_committed); + expect_value(__wrap_d_tm_dec_gauge, value, DBD_BLOB_DF_CAP); + + rc = vos_dtx_aggregate(mock_coh, NULL); + assert_rc_equal(rc, 1); + assert_int_equal(mock_cont_df.cd_newest_aggregated, + EPOCH_OFFSET + CMT_TIME_START + (DBD_BLOB_DF_CAP - 1) * CMT_TIME_STEP); + assert_int_equal(mock_cont_df.cd_dtx_committed_head, mock_dbds_off[1]); + assert_int_equal(mock_cont_df.cd_dtx_committed_tail, mock_dbds_off[DBD_BLOBS_CAP - 1]); + assert_int_equal(mock_cont.vc_dtx_committed_count, DBD_BLOB_DF_CAP * (DBD_BLOBS_CAP - 1)); + assert_int_equal(mock_pool.vp_dtx_committed_count, DBD_BLOB_DF_CAP * (DBD_BLOBS_CAP - 1)); + assert_int_equal(mock_cont.vc_cmt_dtx_reindex_pos, mock_dbds_off[1]); +} + +/* Aggregation of the first 10 committed DTX transactions */ +static void +test_10_entries(void **unused) +{ + uint64_t cmt_time; + int i; + int rc; + + prep_dtx_entries(); + + /* First DTX entries blob */ + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, 0); + for (i = 0; i < DBD_BLOB_DF_CAP; i++) + will_return(__wrap_dbtree_delete, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_newest_aggregated); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_cont_df.cd_newest_aggregated)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_dtx_committed_head); + expect_value(tx_add_ptr, ptr_size, sizeof(umem_off_t)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_dbds[1]->dbd_prev); + expect_value(tx_add_ptr, ptr_size, sizeof(umem_off_t)); + will_return(tx_add_ptr, 0); + expect_value(tx_free, umoff, mock_dbds_off[0]); + will_return(tx_free, 0); + will_return(tx_commit, 0); + expect_value(__wrap_d_tm_dec_gauge, metric, mock_tls.vtl_committed); + expect_value(__wrap_d_tm_dec_gauge, value, DBD_BLOB_DF_CAP); + + /* Second DTX entries blob */ + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, 0); + for (i = 0; i < 3; i++) + will_return(__wrap_dbtree_delete, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_newest_aggregated); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_cont_df.cd_newest_aggregated)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_dbds[1]->dbd_committed_data[0]); + expect_value(tx_add_ptr, ptr_size, + sizeof(mock_dbds[1]->dbd_committed_data[0]) * mock_dbds[1]->dbd_count); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_dbds[1]->dbd_count); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_dbds[1]->dbd_count)); + will_return(tx_add_ptr, 0); + will_return(tx_commit, 0); + expect_value(__wrap_d_tm_dec_gauge, metric, mock_tls.vtl_committed); + expect_value(__wrap_d_tm_dec_gauge, value, 3); + + cmt_time = CMT_TIME_START + (DBD_BLOB_DF_CAP + 2) * CMT_TIME_STEP; + do { + rc = vos_dtx_aggregate(mock_coh, &cmt_time); + assert_true(rc >= 0); + } while (rc > 0); + assert_int_equal(mock_cont_df.cd_newest_aggregated, EPOCH_OFFSET + cmt_time); + assert_int_equal(mock_cont_df.cd_dtx_committed_head, mock_dbds_off[1]); + assert_int_equal(mock_cont_df.cd_dtx_committed_tail, mock_dbds_off[DBD_BLOBS_CAP - 1]); + assert_int_equal(mock_cont.vc_dtx_committed_count, DBD_BLOB_DF_CAP * 2 - 3); + assert_int_equal(mock_pool.vp_dtx_committed_count, DBD_BLOB_DF_CAP * 2 - 3); + assert_int_equal(mock_cont.vc_cmt_dtx_reindex_pos, mock_dbds_off[1]); +} + +/* Aggregation of all the committed DTX transactions */ +static void +test_all_entries(void **unused) +{ + int i; + int rc; + + prep_dtx_entries(); + + for (i = 0; i < DBD_BLOBS_CAP; i++) { + int j; + + will_return(__wrap_vos_tls_get, &mock_tls); + will_return(tx_begin, 0); + for (j = 0; j < DBD_BLOB_DF_CAP; j++) + will_return(__wrap_dbtree_delete, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_newest_aggregated); + expect_value(tx_add_ptr, ptr_size, sizeof(mock_cont_df.cd_newest_aggregated)); + will_return(tx_add_ptr, 0); + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_dtx_committed_head); + expect_value(tx_add_ptr, ptr_size, sizeof(umem_off_t)); + will_return(tx_add_ptr, 0); + if (i == DBD_BLOBS_CAP - 1) + expect_value(tx_add_ptr, ptr, &mock_cont_df.cd_dtx_committed_tail); + else + expect_value(tx_add_ptr, ptr, &mock_dbds[i + 1]->dbd_prev); + expect_value(tx_add_ptr, ptr_size, sizeof(umem_off_t)); + will_return(tx_add_ptr, 0); + expect_value(tx_free, umoff, mock_dbds_off[i]); + will_return(tx_free, 0); + will_return(tx_commit, 0); + expect_value(__wrap_d_tm_dec_gauge, metric, mock_tls.vtl_committed); + expect_value(__wrap_d_tm_dec_gauge, value, DBD_BLOB_DF_CAP); + } + + do { + rc = vos_dtx_aggregate(mock_coh, NULL); + assert_true(rc >= 0); + } while (rc > 0); + assert_int_equal(mock_cont_df.cd_newest_aggregated, + EPOCH_OFFSET + CMT_TIME_START + (3 * DBD_BLOB_DF_CAP - 1) * CMT_TIME_STEP); + assert_int_equal(mock_cont_df.cd_dtx_committed_head, UMOFF_NULL); + assert_int_equal(mock_cont_df.cd_dtx_committed_tail, UMOFF_NULL); + assert_int_equal(mock_cont.vc_dtx_committed_count, 0); + assert_int_equal(mock_pool.vp_dtx_committed_count, 0); + assert_int_equal(mock_cont.vc_cmt_dtx_reindex_pos, UMOFF_NULL); +} + +/* clang-format off */ +/* compilation unit's entry point */ +#define TEST(name, func, setup_func, teardown_func) \ +{ \ + name ": vos_dtx_aggregate - " #func, func, setup_func, teardown_func \ +} +/* clang-format on */ + +static const struct CMUnitTest vos_dtx_aggregate_tests_all[] = { + TEST("DTX500", test_asserts, test_setup, test_teardown), + TEST("DTX501", test_tx_begin_error, test_setup, test_teardown), + TEST("DTX502", test_dbtree_delete_error, test_setup, test_teardown), + TEST("DTX503", test_newest_aggregated_error, test_setup, test_teardown), + TEST("DTX504", test_committed_head_error, test_setup, test_teardown), + TEST("DTX505", test_committed_prev_error, test_setup, test_teardown), + TEST("DTX506", test_umm_free_error, test_setup, test_teardown), + TEST("DTX507", test_committed_data_error, test_setup, test_teardown), + TEST("DTX508", test_dbd_count_error, test_setup, test_teardown), + TEST("DTX509", test_umm_commit_error, test_setup, test_teardown), + TEST("DTX550", test_empty, test_empty_setup, test_empty_teardown), + TEST("DTX551", test_empty_blob, test_setup, test_teardown), + TEST("DTX552", test_one_blob, test_setup, test_teardown), + TEST("DTX553", test_10_entries, test_setup, test_teardown), + TEST("DTX554", test_all_entries, test_setup, test_teardown), +}; + +int +run_dtx_aggregate_tests(void) +{ + const char *test_name = "vos_dtx_aggregate"; + + return cmocka_run_group_tests_name(test_name, vos_dtx_aggregate_tests_all, NULL, NULL); +} diff --git a/src/dtx/tests/dts_cmt_stat.c b/src/dtx/tests/dts_cmt_stat.c new file mode 100644 index 00000000000..dfcac14b09a --- /dev/null +++ b/src/dtx/tests/dts_cmt_stat.c @@ -0,0 +1,190 @@ +/** + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP. + * + * SPDX-License-Identifier: BSD-2-Clause-Patent + */ +#define D_LOGFAC DD_FAC(tests) + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "vos_internal.h" + +/* mocks */ + +#define DTX_CMT_BLOB_MAGIC 0x2502191c +#define DBD_BLOBS_CAP 0x3 +#define DBD_BLOB_DF_CAP 0x8 +#define CMT_TIME_START 0x1000 +#define CMT_TIME_END 0x10000 +#define CMT_TIME_STEP 0x10 +#define CELL_SIZE \ + sizeof(struct vos_dtx_blob_df) + DBD_BLOB_DF_CAP * sizeof(struct vos_dtx_cmt_ent_df) + +static const daos_epoch_t epoch_offsets[] = {0x20000, 0x40000}; + +static struct vos_pool mock_pool; +static struct vos_container mock_cont; +static struct vos_cont_df mock_cont_df; +static struct vos_dtx_blob_df *mock_dbds[DBD_BLOBS_CAP]; +static umem_off_t mock_dbds_off[DBD_BLOBS_CAP]; +static daos_handle_t mock_coh; + +/* Helpers */ + +static void +prep_dtx_entries(void) +{ + int i; + uint64_t cmt_time = CMT_TIME_START; + + for (i = 0; i < DBD_BLOBS_CAP; i++) { + int j; + + for (j = 0; j < DBD_BLOB_DF_CAP; j++) { + struct vos_dtx_cmt_ent_df *dce_df; + + dce_df = &mock_dbds[i]->dbd_committed_data[j]; + dce_df->dce_cmt_time = cmt_time; + dce_df->dce_epoch = cmt_time + epoch_offsets[j % 2]; + cmt_time += CMT_TIME_STEP; + } + mock_dbds[i]->dbd_count = DBD_BLOB_DF_CAP; + } + mock_cont.vc_dtx_committed_count = DBD_BLOBS_CAP * DBD_BLOB_DF_CAP; + mock_pool.vp_dtx_committed_count = DBD_BLOBS_CAP * DBD_BLOB_DF_CAP; +} + +/* setup & teardown */ + +static int +test_setup(void **unused) +{ + int i; + + memset(&mock_pool, 0, sizeof(mock_pool)); + memset(&mock_cont, 0, sizeof(mock_cont)); + memset(&mock_cont_df, 0, sizeof(mock_cont_df)); + + for (i = 0; i < DBD_BLOBS_CAP; i++) { + D_ALLOC(mock_dbds[i], CELL_SIZE); + assert_non_null(mock_dbds[i]); + mock_dbds_off[i] = umem_ptr2off(&mock_pool.vp_umm, mock_dbds[i]); + } + + for (i = 0; i < DBD_BLOBS_CAP; i++) { + mock_dbds[i]->dbd_magic = DTX_CMT_BLOB_MAGIC; + mock_dbds[i]->dbd_cap = DBD_BLOB_DF_CAP; + mock_dbds[i]->dbd_next = + (i == DBD_BLOBS_CAP - 1) ? UMOFF_NULL : mock_dbds_off[i + 1]; + mock_dbds[i]->dbd_prev = (i == 0) ? UMOFF_NULL : mock_dbds_off[i - 1]; + mock_dbds[i]->dbd_count = (i + 1) * 2; + assert_true(mock_dbds[i]->dbd_count <= DBD_BLOB_DF_CAP); + } + + mock_cont.vc_pool = &mock_pool; + mock_cont.vc_cont_df = &mock_cont_df; + mock_cont_df.cd_dtx_committed_head = mock_dbds_off[0]; + mock_cont_df.cd_dtx_committed_tail = mock_dbds_off[DBD_BLOBS_CAP - 1]; + mock_coh.cookie = (uint64_t)&mock_cont; + + return 0; +} + +static int +test_teardown(void **unused) +{ + int i; + + for (i = 0; i < DBD_BLOBS_CAP; i++) + D_FREE(mock_dbds[i]); + + return 0; +} + +/* tests */ + +static void +test_errors(void **unused) +{ + daos_handle_t hdl_null = {0}; + uint64_t cmt_cnt; + int rc; + + /* Invalid arguments. */ + rc = vos_dtx_get_cmt_stat(hdl_null, &cmt_cnt, NULL); + assert_rc_equal(rc, -DER_INVAL); + rc = vos_dtx_get_cmt_stat(mock_coh, NULL, NULL); + assert_rc_equal(rc, -DER_INVAL); + + /* Corrupted dbd */ + mock_dbds[1]->dbd_magic = 42; + rc = vos_dtx_get_cmt_stat(mock_coh, &cmt_cnt, NULL); + assert_rc_equal(rc, -DER_INVAL); +} + +static void +test_cmt_cnt(void **unused) +{ + uint64_t cmt_cnt; + int rc; + + prep_dtx_entries(); + + rc = vos_dtx_get_cmt_stat(mock_coh, &cmt_cnt, NULL); + assert_rc_equal(rc, 0); + assert_int_equal(cmt_cnt, DBD_BLOB_DF_CAP * DBD_BLOBS_CAP); +} + +static void +test_time_stat(void **unused) +{ + uint64_t cmt_cnt; + struct dtx_time_stat dts; + int rc; + + prep_dtx_entries(); + + rc = vos_dtx_get_cmt_stat(mock_coh, &cmt_cnt, &dts); + assert_rc_equal(rc, 0); + assert_int_equal(cmt_cnt, DBD_BLOB_DF_CAP * DBD_BLOBS_CAP); + + assert_int_equal(dts.dts_cmt_time[0], mock_dbds[0]->dbd_committed_data[0].dce_cmt_time); + assert_int_equal(dts.dts_cmt_time[1], mock_dbds[2]->dbd_committed_data[7].dce_cmt_time); + assert_int_equal(dts.dts_cmt_time[2], (dts.dts_cmt_time[0] + dts.dts_cmt_time[1]) / 2); + + assert_int_equal(dts.dts_epoch[0], mock_dbds[0]->dbd_committed_data[0].dce_epoch); + assert_int_equal(dts.dts_epoch[1], mock_dbds[2]->dbd_committed_data[7].dce_epoch); + assert_int_equal(dts.dts_epoch[2], (dts.dts_epoch[0] + dts.dts_epoch[1]) / 2); +} + +/* clang-format off */ +/* compilation unit's entry point */ +#define TEST(name, func) \ +{ \ + name ": vos_dtx_get_cmt_stat - " #func, func, test_setup, test_teardown \ +} +/* clang-format on */ + +static const struct CMUnitTest vos_dtx_get_cmt_stat_tests_all[] = { + TEST("DTX600", test_errors), + TEST("DTX601", test_cmt_cnt), + TEST("DTX602", test_time_stat), +}; + +int +run_dtx_cmt_stat_tests(void) +{ + const char *test_name = "vos_dtx_get_cmt_stat"; + + return cmocka_run_group_tests_name(test_name, vos_dtx_get_cmt_stat_tests_all, NULL, NULL); +} diff --git a/src/dtx/tests/dts_count.c b/src/dtx/tests/dts_count.c deleted file mode 100644 index 3e6b823caf2..00000000000 --- a/src/dtx/tests/dts_count.c +++ /dev/null @@ -1,140 +0,0 @@ -/** - * (C) Copyright 2025 Hewlett Packard Enterprise Development LP. - * - * SPDX-License-Identifier: BSD-2-Clause-Patent - */ -#define D_LOGFAC DD_FAC(tests) - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "vos_internal.h" - -/* mocks */ - -#define DTX_CMT_BLOB_MAGIC 0x2502191c -#define DBD_COUNT 0x3 -#define DBD_BLOB_DF_CAP 0x8 -#define CELL_SIZE \ - sizeof(struct vos_dtx_blob_df) + DBD_BLOB_DF_CAP * sizeof(struct vos_dtx_cmt_ent_df) - -static struct vos_pool mock_pool; -static struct vos_container mock_cont; -static struct vos_cont_df mock_cont_df; -static struct vos_dtx_blob_df *mock_dbds[DBD_COUNT]; -static umem_off_t mock_dbds_off[DBD_COUNT]; -static daos_handle_t mock_coh; - -/* setup & teardown */ - -static int -test_setup(void **unused) -{ - int i; - - memset(&mock_pool, 0, sizeof(mock_pool)); - memset(&mock_cont, 0, sizeof(mock_cont)); - memset(&mock_cont_df, 0, sizeof(mock_cont_df)); - - for (i = 0; i < DBD_COUNT; i++) { - D_ALLOC(mock_dbds[i], CELL_SIZE); - assert_non_null(mock_dbds[i]); - } - - for (i = 0; i < DBD_COUNT; i++) { - mock_dbds[i]->dbd_magic = DTX_CMT_BLOB_MAGIC; - mock_dbds[i]->dbd_cap = DBD_BLOB_DF_CAP; - if (i == DBD_COUNT - 1) - mock_dbds[i]->dbd_next = UMOFF_NULL; - else - mock_dbds[i]->dbd_next = umem_ptr2off(&mock_pool.vp_umm, mock_dbds[i + 1]); - if (i == 0) - mock_dbds[i]->dbd_prev = UMOFF_NULL; - else - mock_dbds[i]->dbd_prev = umem_ptr2off(&mock_pool.vp_umm, mock_dbds[i - 1]); - mock_dbds[i]->dbd_count = (i + 1) * 2; - assert_true(mock_dbds[i]->dbd_count <= DBD_BLOB_DF_CAP); - - mock_dbds_off[i] = umem_ptr2off(&mock_pool.vp_umm, mock_dbds[i]); - } - - mock_cont.vc_pool = &mock_pool; - mock_cont.vc_cont_df = &mock_cont_df; - mock_cont_df.cd_dtx_committed_head = mock_dbds_off[0]; - mock_cont_df.cd_dtx_committed_tail = mock_dbds_off[DBD_COUNT - 1]; - mock_coh.cookie = (uint64_t)&mock_cont; - - return 0; -} - -static int -test_teardown(void **unused) -{ - int i; - - for (i = 0; i < DBD_COUNT; i++) - D_FREE(mock_dbds[i]); - - return 0; -} - -/* tests */ - -static void -test_asserts(void **unused) -{ - daos_handle_t hdl_null = {0}; - uint32_t cnt; - int rc; - - /* Invalid arguments. */ - rc = vos_dtx_get_cmt_cnt(hdl_null, &cnt); - assert_rc_equal(rc, -DER_INVAL); - rc = vos_dtx_get_cmt_cnt(mock_coh, NULL); - assert_rc_equal(rc, -DER_INVAL); - - /* Corrupted dbd */ - mock_dbds[1]->dbd_magic = 42; - rc = vos_dtx_get_cmt_cnt(mock_coh, &cnt); - assert_rc_equal(rc, -DER_INVAL); -} - -static void -test_count(void **unused) -{ - uint32_t cnt; - int rc; - - /* Container with several DTX entries table */ - rc = vos_dtx_get_cmt_cnt(mock_coh, &cnt); - assert_rc_equal(rc, 0); - assert_int_equal(cnt, DBD_COUNT * (DBD_COUNT + 1)); -} - -/* compilation unit's entry point */ -#define TEST(name, func) \ - { \ - name ": vos_dtx_get_cmt_cnt - " #func, func, test_setup, test_teardown \ - } - -static const struct CMUnitTest vos_dtx_count_tests_all[] = { - TEST("DTX600", test_asserts), - TEST("DTX601", test_count), -}; - -int -run_dtx_count_tests(void) -{ - const char *test_name = "vos_dtx_get_cmt_cnt"; - - return cmocka_run_group_tests_name(test_name, vos_dtx_count_tests_all, NULL, NULL); -} diff --git a/src/dtx/tests/dtx_ut.c b/src/dtx/tests/dtx_ut.c index dcccb713161..c4fc0ca8e96 100644 --- a/src/dtx/tests/dtx_ut.c +++ b/src/dtx/tests/dtx_ut.c @@ -22,7 +22,9 @@ run_structs_tests(void); int run_discard_invalid_tests(void); int -run_dtx_count_tests(void); +run_dtx_cmt_stat_tests(void); +int +run_dtx_aggregate_tests(void); static void print_usage() @@ -42,7 +44,8 @@ run_all_tests(int keys) failed += run_structs_tests(); failed += run_discard_invalid_tests(); - failed += run_dtx_count_tests(); + failed += run_dtx_cmt_stat_tests(); + failed += run_dtx_aggregate_tests(); return failed; } diff --git a/src/include/daos_srv/vos.h b/src/include/daos_srv/vos.h index 1b798b5c4c9..2e798dc8a9f 100644 --- a/src/include/daos_srv/vos.h +++ b/src/include/daos_srv/vos.h @@ -235,12 +235,15 @@ vos_dtx_set_flags(daos_handle_t coh, struct dtx_id dtis[], int count, uint32_t f /** * Aggregate the committed DTXs. * - * \param coh [IN] Container open handle. + * \param coh [IN] Container open handle. + * \param cmt_time [IN] The upper commit time to aggregate, or NULL. * - * \return Zero on success, negative value if error. + * \return Zero if all qualified DTX entries have been removed. + * Positive value if more DTX entries can be eventually aggregated. + * Negative value for error. */ int -vos_dtx_aggregate(daos_handle_t coh); +vos_dtx_aggregate(daos_handle_t coh, const uint64_t *cmt_time); /** * Query the container's DTXs statistics information. @@ -1777,15 +1780,23 @@ vos_pin_objects(daos_handle_t coh, daos_unit_oid_t oids[], int count, struct vos bool vos_oi_exist(daos_handle_t coh, daos_unit_oid_t oid); +/* Timing statistic of DTX entries */ +#define DTX_TIME_STAT_COUNT 3 +struct dtx_time_stat { + daos_epoch_t dts_epoch[DTX_TIME_STAT_COUNT]; + uint64_t dts_cmt_time[DTX_TIME_STAT_COUNT]; +}; + /** - * Return the number of DTX committed entries of a container. + * Return statistics on the DTX committed entries of a container. * * \param[in] coh container open handle. - * \param[out] cnt number of DTX committed entries. + * \param[out] cmt_cnt number of DTX committed entries. + * \param[out] dts Timing statistics on the DTX committed entries. * * \return 0 on success, error otherwise. */ int -vos_dtx_get_cmt_cnt(daos_handle_t coh, uint32_t *cnt); +vos_dtx_get_cmt_stat(daos_handle_t coh, uint64_t *cmt_cnt, struct dtx_time_stat *dts); #endif /* __VOS_API_H */ diff --git a/src/utils/ddb/ddb.c b/src/utils/ddb/ddb.c index e78a1ac0437..a0e975ad97d 100644 --- a/src/utils/ddb/ddb.c +++ b/src/utils/ddb/ddb.c @@ -48,6 +48,7 @@ #define COMMAND_NAME_DEV_REPLACE "dev_replace" #define COMMAND_NAME_DTX_STAT "dtx_stat" #define COMMAND_NAME_PROV_MEM "prov_mem" +#define COMMAND_NAME_DTX_AGGR "dtx_aggr" /* Parse command line options for the 'ls' command */ static int @@ -915,6 +916,86 @@ prov_mem_option_parse(struct ddb_ctx *ctx, struct prov_mem_options *cmd_args, ui return 0; } +/* Parse command line options for the 'dtx_aggr' command */ +static int +dtx_aggr_option_parse(struct ddb_ctx *ctx, struct dtx_aggr_options *cmd_args, uint32_t argc, + char **argv) +{ + int opt; + char *options_short = "t:d:"; + int index = 0; + char *endptr; + struct option options_long[] = {{"cmt_time", required_argument, NULL, 't'}, + {"cmt_date", required_argument, NULL, 'd'}, + {NULL}}; + + memset(cmd_args, 0, sizeof(*cmd_args)); + /* Restart getopt */ + optind = 1; + opterr = 0; + cmd_args->format = DDB_DTX_AGGR_NOW; + while ((opt = getopt_long(argc, argv, options_short, options_long, &index)) != -1) { + switch (opt) { + case 't': + if (cmd_args->format == DDB_DTX_AGGR_CMT_DATE) { + ddb_error(ctx, "'--cmt_time' and '--cmt_date' options are mutually " + "exclusive\n"); + return -DER_INVAL; + } + if (cmd_args->format == DDB_DTX_AGGR_CMT_TIME) { + ddb_error(ctx, + "'--cmt_time' option can not be used multiple time\n"); + return -DER_INVAL; + } + errno = 0; + cmd_args->cmt_time = strtoull(optarg, &endptr, 10); + if (errno != 0 || endptr == optarg || *endptr != '\0') { + ddb_error(ctx, "'--cmt_time' option arg format is invalid\n"); + return -DER_INVAL; + } + cmd_args->format = DDB_DTX_AGGR_CMT_TIME; + break; + case 'd': + if (cmd_args->format == DDB_DTX_AGGR_CMT_TIME) { + ddb_error(ctx, "'--cmt_time' and '--cmt_date' options are mutually " + "exclusive\n"); + return -DER_INVAL; + } + if (cmd_args->format == DDB_DTX_AGGR_CMT_DATE) { + ddb_error(ctx, + "'--cmt_date' option can not be used multiple time\n"); + return -DER_INVAL; + } + cmd_args->cmt_date = optarg; + cmd_args->format = DDB_DTX_AGGR_CMT_DATE; + break; + case '?': + ddb_printf(ctx, "Unknown option: '%c'\n", optopt); + break; + default: + return -DER_INVAL; + } + } + if (cmd_args->format == DDB_DTX_AGGR_NOW) { + ddb_error(ctx, "'--cmt_time' or '--cmt_date' option has to be defined\n"); + return -DER_INVAL; + } + + index = optind; + cmd_args->path = NULL; + if (argc - index > 0 && *argv[index] != '\0') { + cmd_args->path = argv[index]; + index++; + } + + if (argc - index > 0) { + ddb_errorf(ctx, "Unexpected argument: %s\n", argv[index]); + return -DER_INVAL; + } + + return 0; +} + int ddb_parse_cmd_args(struct ddb_ctx *ctx, uint32_t argc, char **argv, struct ddb_cmd_info *info) { @@ -1046,6 +1127,11 @@ ddb_parse_cmd_args(struct ddb_ctx *ctx, uint32_t argc, char **argv, struct ddb_c return prov_mem_option_parse(ctx, &info->dci_cmd_option.dci_prov_mem, argc, argv); } + if (same(cmd, COMMAND_NAME_DTX_AGGR)) { + info->dci_cmd = DDB_CMD_DTX_AGGR; + return dtx_aggr_option_parse(ctx, &info->dci_cmd_option.dci_dtx_aggr, argc, argv); + } + ddb_errorf(ctx, "'%s' is not a valid command. Available commands are:" "'help', " @@ -1074,7 +1160,7 @@ ddb_parse_cmd_args(struct ddb_ctx *ctx, uint32_t argc, char **argv, struct ddb_c "'dev_replace', " "'dtx_stat', " "'prov_mem', " - "\n", + "'dtx_aggr'\n", cmd); return -DER_INVAL; @@ -1258,6 +1344,10 @@ ddb_run_cmd(struct ddb_ctx *ctx, const char *cmd_str) rc = ddb_run_prov_mem(ctx, &info.dci_cmd_option.dci_prov_mem); break; + case DDB_CMD_DTX_AGGR: + rc = ddb_run_dtx_aggr(ctx, &info.dci_cmd_option.dci_dtx_aggr); + break; + case DDB_CMD_UNKNOWN: ddb_error(ctx, "Unknown command\n"); rc = -DER_INVAL; @@ -1472,7 +1562,7 @@ ddb_commands_help(struct ddb_ctx *ctx) ddb_print(ctx, "dtx_stat [path]\n"); ddb_print(ctx, "\tPrint statistic on the DTX entries.\n"); ddb_print(ctx, " [path]\n"); - ddb_print(ctx, "\tOptional, VOS tree path to query.\n"); + ddb_print(ctx, "\tOptional, VOS tree path of a container to query.\n"); ddb_print(ctx, "\n"); /* Command: prov_mem */ @@ -1487,6 +1577,18 @@ ddb_commands_help(struct ddb_ctx *ctx) ddb_print(ctx, " \n"); ddb_print(ctx, "\tPath to the tmpfs mountpoint.\n"); ddb_print(ctx, "\n"); + + /* Command: dtx_aggr */ + ddb_print(ctx, "dtx_aggr [path]\n"); + ddb_print(ctx, "\tAggregate DTX entries until a given timestamp or duration.\n"); + ddb_print(ctx, " [path]\n"); + ddb_print(ctx, "\tOptional, VOS tree path of a container to aggregate.\n"); + ddb_print(ctx, "Options:\n"); + ddb_print(ctx, " -t, --cmt_time\n"); + ddb_print(ctx, "\tMax aggregation commit time\n"); + ddb_print(ctx, " -d, --cmt_date\n"); + ddb_print(ctx, "\tMax aggregation commit date (format '1970-01-01 00:00:00')\n"); + ddb_print(ctx, "\n"); } void @@ -1557,4 +1659,6 @@ ddb_program_help(struct ddb_ctx *ctx) ddb_print(ctx, " dev_list List all devices\n"); ddb_print(ctx, " dev_replace Replace an old device with a new unused device\n"); ddb_print(ctx, " dtx_stat Stat on DTX entries\n"); + ddb_print(ctx, " prov_mem Prepare memory environment for md-on-ssd mode.\n"); + ddb_print(ctx, " dtx_aggr Aggregate DTX entries\n"); } diff --git a/src/utils/ddb/ddb.h b/src/utils/ddb/ddb.h index 6ff83cc2b7d..3bc63c8f40f 100644 --- a/src/utils/ddb/ddb.h +++ b/src/utils/ddb/ddb.h @@ -125,6 +125,7 @@ enum ddb_cmd { DDB_CMD_DEV_REPLACE = 25, DDB_CMD_DTX_STAT = 26, DDB_CMD_PROV_MEM = 27, + DDB_CMD_DTX_AGGR = 28, }; /* option and argument structures for commands that need them */ @@ -217,6 +218,7 @@ struct dev_replace_options { struct dtx_stat_options { char *path; + bool details; }; struct prov_mem_options { @@ -225,6 +227,15 @@ struct prov_mem_options { unsigned int tmpfs_mount_size; }; +enum dtx_aggr_format { DDB_DTX_AGGR_NOW = 0, DDB_DTX_AGGR_CMT_TIME = 1, DDB_DTX_AGGR_CMT_DATE = 2 }; + +struct dtx_aggr_options { + char *path; + enum dtx_aggr_format format; + uint64_t cmt_time; + char *cmt_date; +}; + struct ddb_cmd_info { enum ddb_cmd dci_cmd; union { @@ -247,6 +258,7 @@ struct ddb_cmd_info { struct dev_replace_options dci_dev_replace; struct dtx_stat_options dci_dtx_stat; struct prov_mem_options dci_prov_mem; + struct dtx_aggr_options dci_dtx_aggr; } dci_cmd_option; }; @@ -316,6 +328,8 @@ int ddb_run_dtx_stat(struct ddb_ctx *ctx, struct dtx_stat_options *opt); int ddb_run_prov_mem(struct ddb_ctx *ctx, struct prov_mem_options *opt); +int +ddb_run_dtx_aggr(struct ddb_ctx *ctx, struct dtx_aggr_options *opt); void ddb_program_help(struct ddb_ctx *ctx); diff --git a/src/utils/ddb/ddb_commands.c b/src/utils/ddb/ddb_commands.c index e95fbe5dc0a..0e26686a24c 100644 --- a/src/utils/ddb/ddb_commands.c +++ b/src/utils/ddb/ddb_commands.c @@ -1288,34 +1288,26 @@ ddb_run_dev_replace(struct ddb_ctx *ctx, struct dev_replace_options *opt) return rc; } -enum { TIMESPEC_FMT_SIZE = sizeof("1970-01-01 00:00:00.000000000") }; +enum { TIMESPEC_FMT_SIZE = sizeof("1970-01-01 00:00:00") }; static int timespec2str(struct timespec *tspec, char *buf, size_t buf_size) { struct tm date; size_t buf_len; - int rc; - tzset(); if (localtime_r(&(tspec->tv_sec), &date) == NULL) return d_errno2der(errno); buf_len = strftime(buf, buf_size, "%F %T", &date); - if (buf_len == 0) + if (buf_len != buf_size - 1) return -DER_INVAL; - rc = snprintf(&buf[buf_len], buf_size - buf_len, ".%09ld", tspec->tv_nsec); - if (rc < 0) - return -DER_INVAL; - if (rc >= buf_size - buf_len) - return -DER_TRUNC; - return 0; } static int -dtx_print_time_stat(struct ddb_ctx *ctx, char *stat_name, uint64_t epoch, char *align) +dtx_print_epoch(struct ddb_ctx *ctx, const daos_epoch_t epoch) { struct timespec tspec; char buf[TIMESPEC_FMT_SIZE]; @@ -1323,55 +1315,162 @@ dtx_print_time_stat(struct ddb_ctx *ctx, char *stat_name, uint64_t epoch, char * rc = d_hlc2timespec(epoch, &tspec); if (!SUCCESS(rc)) { - ddb_errorf(ctx, "Conversion error of the DTX stat %s: " DF_RC "\n", stat_name, - DP_RC(rc)); - return rc; + ddb_errorf(ctx, "Failed to convert DTX epoch %" PRIu64 " to timespec: " DF_RC "\n", + epoch, DP_RC(rc)); + goto out; } + rc = timespec2str(&tspec, buf, TIMESPEC_FMT_SIZE); - if (!SUCCESS(rc)) { - ddb_errorf(ctx, "Conversion error of the DTX stat %s: " DF_RC "\n", stat_name, - DP_RC(rc)); - return rc; + + ddb_printf(ctx, "%s (%" PRIu64 ")", buf, epoch); +out: + return rc; +} + +static const char *stat_names[] = {"min", "max", "mean"}; + +static int +dtx_print_epoch_stat(struct ddb_ctx *ctx, const char *prefix, const daos_epoch_t epoch_stats[], + const char *align) +{ + int i; + int rc; + + ddb_printf(ctx, "\t- %s%s", prefix, align); + + rc = 0; + for (i = 0; i < DTX_TIME_STAT_COUNT; i++) { + if (epoch_stats[i] == 0 || epoch_stats[i] == DAOS_EPOCH_MAX) { + ddb_printf(ctx, "%s=NA (NA)", stat_names[i]); + } else { + ddb_printf(ctx, "%s=", stat_names[i]); + rc = dtx_print_epoch(ctx, epoch_stats[i]); + if (!SUCCESS(rc)) + goto out; + } + if (i < DTX_TIME_STAT_COUNT - 1) + ddb_print(ctx, ", "); } - ddb_printf(ctx, "\t- %s time:%s%s, %" PRIu64 "\n", stat_name, align, buf, epoch); + ddb_print(ctx, "\n"); - return 0; +out: + return rc; +} + +static int +dtx_print_time_stat(struct ddb_ctx *ctx, const char *prefix, const uint64_t cmt_time_stats[], + const char *align) +{ + int i; + int rc; + + ddb_printf(ctx, "\t- %s%s", prefix, align); + + rc = 0; + for (i = 0; i < DTX_TIME_STAT_COUNT; i++) { + if (cmt_time_stats[i] == 0 || cmt_time_stats[i] == UINT64_MAX) { + ddb_printf(ctx, "%s=NA (NA)", stat_names[i]); + } else { + struct timespec tspec = {0}; + char buf[TIMESPEC_FMT_SIZE]; + + tspec.tv_sec = cmt_time_stats[i]; + rc = timespec2str(&tspec, buf, TIMESPEC_FMT_SIZE); + if (!SUCCESS(rc)) { + ddb_errorf(ctx, + "Failed to convert DTX commit time %" PRIu64 + " to timespec: " DF_RC "\n", + cmt_time_stats[i], DP_RC(rc)); + goto out; + } + ddb_printf(ctx, "%s=%s (%" PRIu64 ")", stat_names[i], buf, + cmt_time_stats[i]); + } + if (i < DTX_TIME_STAT_COUNT - 1) + ddb_print(ctx, ", "); + } + ddb_print(ctx, "\n"); + +out: + return rc; } struct dtx_stat_args { struct ddb_ctx *ctx; struct dtx_stat_options *opt; uint32_t cont_seen; - uint32_t cmt_cnt; + uint64_t cmt_cnt; + struct dtx_time_stat time_stat; + daos_epoch_t aggr_epoch; }; static int -dtx_stat(struct ddb_ctx *ctx, struct dv_indexed_tree_path *itp, uint32_t *cnt) +dtx_stat_print(struct ddb_ctx *ctx, uint64_t cmt_cnt, const struct dtx_time_stat *dts, + daos_epoch_t aggr_epoch) +{ + int rc = 0; + + ddb_printf(ctx, "\t- Committed DTX count:\t%" PRIu64 "\n", cmt_cnt); + if (dts != NULL) { + rc = dtx_print_time_stat(ctx, "Committed DTX time:", dts->dts_cmt_time, "\t"); + if (!SUCCESS(rc)) + goto done; + rc = dtx_print_epoch_stat(ctx, "Committed DTX epoch:", dts->dts_epoch, "\t"); + if (!SUCCESS(rc)) + goto done; + } + ddb_print(ctx, "\t- DTX aggregated epoch:\t"); + if (aggr_epoch == 0 || aggr_epoch == DAOS_EPOCH_MAX) + ddb_print(ctx, "NA (NA)"); + else { + rc = dtx_print_epoch(ctx, aggr_epoch); + if (!SUCCESS(rc)) + goto done; + } + ddb_print(ctx, "\n"); + +done: + + return rc; +} + +static int +dtx_stat_cont(struct ddb_ctx *ctx, struct dv_indexed_tree_path *itp, struct dtx_stat_args *args) { - struct dtx_stat stat; daos_handle_t coh = {0}; - uint32_t cnt_tmp; + uint64_t cmt_cnt_tmp; + struct dtx_time_stat dts_tmp; + struct dtx_stat aggr_stat_tmp; int rc; rc = dv_cont_open(ctx->dc_poh, itp_cont(itp), &coh); if (!SUCCESS(rc)) goto done; - rc = vos_dtx_get_cmt_cnt(coh, &cnt_tmp); + if (args->opt->details) + rc = vos_dtx_get_cmt_stat(coh, &cmt_cnt_tmp, &dts_tmp); + else + rc = vos_dtx_get_cmt_stat(coh, &cmt_cnt_tmp, NULL); if (!SUCCESS(rc)) goto done; - vos_dtx_stat(coh, &stat, 0); + vos_dtx_stat(coh, &aggr_stat_tmp, 0); - ddb_print(ctx, "DTX entries statistics of "); + ddb_print(ctx, "DTX entries statistics of container "); itp_print_full(ctx, itp); ddb_print(ctx, "\n"); - ddb_printf(ctx, "\t- Number of committed DTX of the container:\t%" PRIu32 "\n", cnt_tmp); - rc = - dtx_print_time_stat(ctx, "DTX newest aggregated", stat.dtx_newest_aggregated, "\t\t\t"); + if (args->opt->details) + rc = + dtx_stat_print(ctx, cmt_cnt_tmp, &dts_tmp, aggr_stat_tmp.dtx_newest_aggregated); + else + rc = dtx_stat_print(ctx, cmt_cnt_tmp, NULL, aggr_stat_tmp.dtx_newest_aggregated); if (!SUCCESS(rc)) goto done; - if (cnt != NULL) - *cnt = cnt_tmp; + + args->cmt_cnt = cmt_cnt_tmp; + args->aggr_epoch = aggr_stat_tmp.dtx_newest_aggregated; + if (args->opt->details) + memcpy(&args->time_stat, &dts_tmp, sizeof(struct dtx_time_stat)); + done: dv_cont_close(&coh); @@ -1379,13 +1478,13 @@ dtx_stat(struct ddb_ctx *ctx, struct dv_indexed_tree_path *itp, uint32_t *cnt) } static int -dtx_stat_cb(daos_handle_t ih, vos_iter_entry_t *entry, vos_iter_type_t type, - vos_iter_param_t *param, void *cb_arg, unsigned int *acts) +dtx_stat_cont_cb(daos_handle_t ih, vos_iter_entry_t *entry, vos_iter_type_t type, + vos_iter_param_t *param, void *cb_arg, unsigned int *acts) { struct ddb_ctx *ctx; struct dv_indexed_tree_path itp = {0}; struct dtx_stat_args *args; - uint32_t cnt; + struct dtx_stat_args args_tmp; int rc; if (type != VOS_ITER_COUUID) { @@ -1394,15 +1493,54 @@ dtx_stat_cb(daos_handle_t ih, vos_iter_entry_t *entry, vos_iter_type_t type, } args = (struct dtx_stat_args *)cb_arg; + memcpy(&args_tmp, args, sizeof(struct dtx_stat_args)); ctx = args->ctx; itp_set_cont(&itp, entry->ie_couuid, args->cont_seen); ++(args->cont_seen); - rc = dtx_stat(ctx, &itp, &cnt); + rc = dtx_stat_cont(ctx, &itp, &args_tmp); if (!SUCCESS(rc)) goto done; - args->cmt_cnt += cnt; + + if (args->opt->details) { + if (args->aggr_epoch < args_tmp.aggr_epoch) + args->aggr_epoch = args_tmp.aggr_epoch; + if (args->time_stat.dts_cmt_time[0] > args_tmp.time_stat.dts_cmt_time[0]) + args->time_stat.dts_cmt_time[0] = args_tmp.time_stat.dts_cmt_time[0]; + if (args->time_stat.dts_cmt_time[1] < args_tmp.time_stat.dts_cmt_time[1]) + args->time_stat.dts_cmt_time[1] = args_tmp.time_stat.dts_cmt_time[1]; + if (args->time_stat.dts_cmt_time[2] == 0) + args->time_stat.dts_cmt_time[2] = args_tmp.time_stat.dts_cmt_time[2]; + else { + long double tmp_mean; + + tmp_mean = args->time_stat.dts_cmt_time[2] * (long double)args->cmt_cnt; + tmp_mean += (long double)args_tmp.time_stat.dts_cmt_time[2] * + (long double)args_tmp.cmt_cnt; + tmp_mean /= (long double)(args->cmt_cnt + args_tmp.cmt_cnt); + args->time_stat.dts_cmt_time[2] = tmp_mean; + } + if (args->time_stat.dts_epoch[0] > args_tmp.time_stat.dts_epoch[0]) + args->time_stat.dts_epoch[0] = args_tmp.time_stat.dts_epoch[0]; + if (args->time_stat.dts_epoch[1] < args_tmp.time_stat.dts_epoch[1]) + args->time_stat.dts_epoch[1] = args_tmp.time_stat.dts_epoch[1]; + if (args->time_stat.dts_epoch[2] == 0) + args->time_stat.dts_epoch[2] = args_tmp.time_stat.dts_epoch[2]; + else { + long double tmp_mean; + + tmp_mean = args->time_stat.dts_epoch[2] * (long double)args->cmt_cnt; + tmp_mean += (long double)args_tmp.time_stat.dts_epoch[2] * + (long double)args_tmp.cmt_cnt; + tmp_mean /= (long double)(args->cmt_cnt + args_tmp.cmt_cnt); + args->time_stat.dts_epoch[2] = tmp_mean; + } + } + + args->cmt_cnt += args_tmp.cmt_cnt; + if (args->aggr_epoch < args_tmp.aggr_epoch) + args->aggr_epoch = args_tmp.aggr_epoch; done: itp_free(&itp); @@ -1410,6 +1548,28 @@ dtx_stat_cb(daos_handle_t ih, vos_iter_entry_t *entry, vos_iter_type_t type, return rc; } +static int +dtx_stat_cont_warp(struct ddb_ctx *ctx, struct dtx_stat_args *args) +{ + struct dv_indexed_tree_path itp = {0}; + int rc; + + rc = init_path(ctx, args->opt->path, &itp); + if (!SUCCESS(rc)) + goto done; + + if (!itp_has_cont(&itp)) { + ddb_errorf(ctx, "Invalid container path " DF_PATH ".\n", DP_PATH(args->opt->path)); + rc = -DER_INVAL; + goto done; + } + + rc = dtx_stat_cont(ctx, &itp, args); +done: + itp_free(&itp); + return rc; +} + int ddb_run_dtx_stat(struct ddb_ctx *ctx, struct dtx_stat_options *opt) { @@ -1420,39 +1580,179 @@ ddb_run_dtx_stat(struct ddb_ctx *ctx, struct dtx_stat_options *opt) if (daos_handle_is_inval(ctx->dc_poh)) { ddb_error(ctx, "Not connected to a pool. Use 'open' to connect to a pool.\n"); - return -DER_NONEXIST; + rc = -DER_NONEXIST; + goto done; } + args.ctx = ctx; + args.opt = opt; if (opt->path != NULL && opt->path[0] != '\0') { - struct dv_indexed_tree_path itp = {0}; + rc = dtx_stat_cont_warp(ctx, &args); + goto done; + } - rc = init_path(ctx, opt->path, &itp); - if (!SUCCESS(rc)) - goto done; + args.cmt_cnt = 0; + args.time_stat.dts_cmt_time[0] = UINT64_MAX; + args.time_stat.dts_epoch[0] = DAOS_EPOCH_MAX; + param.ip_hdl = ctx->dc_poh; + param.ip_epr.epr_hi = DAOS_EPOCH_MAX; + do { + rc = vos_iterate(¶m, VOS_ITER_COUUID, false, &anchors, NULL, dtx_stat_cont_cb, + &args, NULL); + } while (rc > 0); + ddb_printf(ctx, "DTX entries statistics of the pool %s\n", ctx->dc_pool_path); + if (opt->details) + rc = dtx_stat_print(ctx, args.cmt_cnt, &args.time_stat, args.aggr_epoch); + else + rc = dtx_stat_print(ctx, args.cmt_cnt, NULL, args.aggr_epoch); + +done: + return rc; +} + +struct dtx_aggr_args { + struct ddb_ctx *ctx; + uint32_t cont_seen; + uint64_t *cmt_time; +}; + +static int +dtx_aggr_cont(struct ddb_ctx *ctx, struct dv_indexed_tree_path *itp, uint64_t *cmt_time) +{ + daos_handle_t coh = {0}; + int rc; + + rc = dv_cont_open(ctx->dc_poh, itp_cont(itp), &coh); + if (!SUCCESS(rc)) { + ddb_error(ctx, "Failed to open container "); + itp_print_full(ctx, itp); + ddb_errorf(ctx, ": " DF_RC "\n", DP_RC(rc)); + return rc; + } + + ddb_print(ctx, "Aggregating DTX entries of container "); + itp_print_full(ctx, itp); + ddb_print(ctx, "...\n"); + do { + rc = vos_dtx_aggregate(coh, cmt_time); + } while (rc > 0); + if (rc < 0) { + ddb_error(ctx, "Failed to aggregate DTX committed entries of container "); + itp_print_full(ctx, itp); + ddb_errorf(ctx, ": " DF_RC "\n", DP_RC(rc)); + } + + rc = dv_cont_close(&coh); + if (!SUCCESS(rc)) { + ddb_error(ctx, "Failed to close container "); + itp_print_full(ctx, itp); + ddb_errorf(ctx, ": " DF_RC "\n", DP_RC(rc)); + } + + return rc; +} + +static int +dtx_aggr_cont_cb(daos_handle_t ih, vos_iter_entry_t *entry, vos_iter_type_t type, + vos_iter_param_t *param, void *cb_arg, unsigned int *acts) +{ + struct dv_indexed_tree_path itp = {0}; + struct dtx_aggr_args *args; + int rc; + + if (type != VOS_ITER_COUUID) { + rc = 0; + goto done; + } + + args = (struct dtx_aggr_args *)cb_arg; + + itp_set_cont(&itp, entry->ie_couuid, args->cont_seen); + ++(args->cont_seen); + + rc = dtx_aggr_cont(args->ctx, &itp, args->cmt_time); + +done: + itp_free(&itp); + + return rc; +} + +static int +dtx_aggr_cont_warp(struct ddb_ctx *ctx, char *path, uint64_t *epoch) +{ + struct dv_indexed_tree_path itp = {0}; + int rc; + + rc = init_path(ctx, path, &itp); + if (!SUCCESS(rc)) + goto done; + + if (!itp_has_cont(&itp)) { + ddb_errorf(ctx, "Invalid container path " DF_PATH ".\n", DP_PATH(path)); + rc = -DER_INVAL; + goto done; + } + + rc = dtx_aggr_cont(ctx, &itp, epoch); + +done: + itp_free(&itp); + + return rc; +} + +int +ddb_run_dtx_aggr(struct ddb_ctx *ctx, struct dtx_aggr_options *opt) +{ + vos_iter_param_t param = {0}; + struct dtx_aggr_args args = {0}; + struct vos_iter_anchors anchors = {0}; + int rc; + + if (!ctx->dc_write_mode) { + ddb_error(ctx, error_msg_write_mode_only); + rc = -DER_INVAL; + goto done; + } + + if (daos_handle_is_inval(ctx->dc_poh)) { + ddb_error(ctx, "Not connected to a pool. Use 'open' to connect to a pool.\n"); + rc = -DER_NONEXIST; + goto done; + } - if (!itp_has_cont(&itp)) { - rc = -DER_INVAL; + switch (opt->format) { + case DDB_DTX_AGGR_NOW: + args.cmt_time = NULL; + break; + case DDB_DTX_AGGR_CMT_DATE: + rc = ddb_date2cmt_time(opt->cmt_date, &opt->cmt_time); + if (!SUCCESS(rc)) { + ddb_error(ctx, "'--date' option arg date format is invalid\n"); goto done; } + case DDB_DTX_AGGR_CMT_TIME: + args.cmt_time = &opt->cmt_time; + break; + default: + D_ASSERT(false && "Invalid dtx_aggr options"); + } - rc = dtx_stat(ctx, &itp, NULL); -done: - itp_free(&itp); - return rc; + if (opt->path != NULL && opt->path[0] != '\0') { + rc = dtx_aggr_cont_warp(ctx, opt->path, args.cmt_time); + goto done; } args.ctx = ctx; - args.opt = opt; param.ip_hdl = ctx->dc_poh; param.ip_epr.epr_hi = DAOS_EPOCH_MAX; do { - rc = vos_iterate(¶m, VOS_ITER_COUUID, false, &anchors, NULL, dtx_stat_cb, &args, - NULL); + rc = vos_iterate(¶m, VOS_ITER_COUUID, false, &anchors, NULL, dtx_aggr_cont_cb, + &args, NULL); } while (rc > 0); - if (rc == 0) - ddb_printf(ctx, "Number of committed DTX of the pool:\t\t\t%" PRIu32 "\n", - args.cmt_cnt); +done: return rc; } diff --git a/src/utils/ddb/ddb_parse.c b/src/utils/ddb/ddb_parse.c index 3ec673dc385..e1bf64272a7 100644 --- a/src/utils/ddb/ddb_parse.c +++ b/src/utils/ddb/ddb_parse.c @@ -481,22 +481,23 @@ ddb_parse_key(const char *input, daos_key_t *key) } int -ddb_date2epoch(const char *date, uint64_t *epoch) +ddb_date2cmt_time(const char *date, uint64_t *cmt_time) { - struct tm date_tm = {0}; - struct timespec date_tspec = {0}; + struct tm date_tm = {0}; char *endptr; + time_t cmt_time_tmp; - if (date == NULL || epoch == NULL) + if (date == NULL || cmt_time == NULL) return -DER_INVAL; endptr = strptime(date, "%Y-%m-%d %H:%M:%S", &date_tm); if (endptr == NULL || *endptr != '\0') return -DER_INVAL; - date_tspec.tv_sec = mktime(&date_tm); - if (date_tspec.tv_sec == (time_t)-1) + cmt_time_tmp = mktime(&date_tm); + if (cmt_time_tmp == (time_t)-1) return daos_errno2der(errno); + *cmt_time = cmt_time_tmp; - return d_timespec2hlc(date_tspec, epoch); + return 0; } diff --git a/src/utils/ddb/ddb_parse.h b/src/utils/ddb/ddb_parse.h index 6fc0e672e14..df5d43771db 100644 --- a/src/utils/ddb/ddb_parse.h +++ b/src/utils/ddb/ddb_parse.h @@ -52,8 +52,8 @@ int ddb_parse_key(const char *input, daos_key_t *key); */ int ddb_parse_dtx_id(const char *dtx_id_str, struct dtx_id *dtx_id); -/* Parse a string representing a date into an epoch */ +/* Parse a string representing a date into a DTX commit time */ int -ddb_date2epoch(const char *date, uint64_t *epoch); +ddb_date2cmt_time(const char *date, uint64_t *cmt_time); #endif /** __DAOS_DDB_PARSE_H */ diff --git a/src/utils/ddb/tests/ddb_cmd_options_tests.c b/src/utils/ddb/tests/ddb_cmd_options_tests.c index 199099abc77..ee106a8cd5f 100644 --- a/src/utils/ddb/tests/ddb_cmd_options_tests.c +++ b/src/utils/ddb/tests/ddb_cmd_options_tests.c @@ -316,6 +316,45 @@ dtx_stat_parsing(void **state) assert_null(options->path); } +static void +dtx_aggr_parsing(void **state) +{ + struct ddb_cmd_info info = {0}; + struct dtx_aggr_options *options = &info.dci_cmd_option.dci_dtx_aggr; + + /* test invalid arguments and options */ + test_run_inval_cmd("dtx_aggr"); + test_run_inval_cmd("dtx_aggr", "path"); + test_run_inval_cmd("dtx_aggr", "path", "extra"); + test_run_inval_cmd("dtx_aggr", "-z"); + test_run_inval_cmd("dtx_aggr", "-t", "foo", "path"); + test_run_inval_cmd("dtx_aggr", "-t", "666", "-t", "999", "path"); + test_run_inval_cmd("dtx_aggr", "-d", "1970-01-01 00:00:00", "-d", "2000-01-01 00:00:00", + "path"); + test_run_inval_cmd("dtx_aggr", "-t", "42", "-d", "1970-01-01 00:00:00", "path"); + + /* test all options and arguments */ + test_run_cmd(&info, "dtx_aggr", "-t", "42"); + assert_null(options->path); + assert_non_null(options->cmt_time); + assert_null(options->cmt_date); + + test_run_cmd(&info, "dtx_aggr", "-d", "1970-01-01 00:00:00"); + assert_null(options->path); + assert_null(options->cmt_time); + assert_non_null(options->cmt_date); + + test_run_cmd(&info, "dtx_aggr", "-t", "42", "path"); + assert_non_null(options->path); + assert_non_null(options->cmt_time); + assert_null(options->cmt_date); + + test_run_cmd(&info, "dtx_aggr", "-d", "1970-01-01 00:00:00", "path"); + assert_non_null(options->path); + assert_null(options->cmt_time); + assert_non_null(options->cmt_date); +} + /* * ----------------------------------------------- * Execute @@ -341,6 +380,7 @@ ddb_cmd_options_tests_run() TEST(dtx_act_commit_options_parsing), TEST(dtx_act_abort_options_parsing), TEST(dtx_stat_parsing), + TEST(dtx_aggr_parsing), }; return cmocka_run_group_tests_name("DDB commands option parsing tests", tests, diff --git a/src/utils/ddb/tests/ddb_cmocka.h b/src/utils/ddb/tests/ddb_cmocka.h index a5948b6a048..567de80f501 100644 --- a/src/utils/ddb/tests/ddb_cmocka.h +++ b/src/utils/ddb/tests/ddb_cmocka.h @@ -120,4 +120,16 @@ fail_msg("'%s' regex not matched in '%s'", regex, str); \ } while (0) +#define assert_nl_equal(str, cnt) \ + do { \ + int _i; \ + int _nl_cnt; \ + _nl_cnt = 0; \ + for (_i = 0; _i <= strlen(str); _i++) { \ + if (str[_i] == '\n') \ + _nl_cnt++; \ + } \ + assert_int_equal(_nl_cnt++, cnt); \ + } while (0) + #endif /* DAOS_DDB_CMOCKA_H */ diff --git a/src/utils/ddb/tests/ddb_commands_tests.c b/src/utils/ddb/tests/ddb_commands_tests.c index c6ca6ea8c2a..acb920d9fce 100644 --- a/src/utils/ddb/tests/ddb_commands_tests.c +++ b/src/utils/ddb/tests/ddb_commands_tests.c @@ -411,6 +411,8 @@ dtx_stat_tests(void **state) struct dt_vos_pool_ctx *tctx = *state; struct ddb_ctx ctx = {0}; struct dtx_stat_options opt = {0}; + int i; + int cont_cnt; ctx.dc_poh = tctx->dvt_poh; ctx.dc_io_ft.ddb_print_message = dvt_fake_print; @@ -419,22 +421,141 @@ dtx_stat_tests(void **state) opt.path = "[0]"; dvt_fake_print_reset(); assert_success(ddb_run_dtx_stat(&ctx, &opt)); + assert_regex_match(dvt_fake_print_buffer, + "^DTX entries statistics of container " + "CONT:[[:blank:]]+\\(/\\[0\\]\\)[[:blank:]]+/[[:digit:]-]+$"); + assert_regex_match(dvt_fake_print_buffer, + "^[[:blank:]]+- Committed DTX count:[[:blank:]]+1$"); + assert_regex_match(dvt_fake_print_buffer, + "^[[:blank:]]+- DTX aggregated epoch:[[:blank:]]+NA \\(NA\\)$"); + assert_nl_equal(dvt_fake_print_buffer, 3); + + opt.details = true; + dvt_fake_print_reset(); + assert_success(ddb_run_dtx_stat(&ctx, &opt)); + assert_regex_match(dvt_fake_print_buffer, + "^DTX entries statistics of container " + "CONT:[[:blank:]]+\\(/\\[0\\]\\)[[:blank:]]+/[[:digit:]-]+$"); + assert_regex_match( + dvt_fake_print_buffer, + "^[[:blank:]]+- Committed DTX time:[[:blank:]]+min=20.+, max=20.+, mean=20.+$"); assert_regex_match( dvt_fake_print_buffer, - "^[[:blank:]]+- Number of committed DTX of the container:[[:blank:]]+1$"); + "^[[:blank:]]+- Committed DTX epoch:[[:blank:]]+min=20.+, max=20.+, mean=20.+$"); assert_regex_match(dvt_fake_print_buffer, - "^[[:blank:]]+- DTX newest aggregated time:.+, 0$"); + "^[[:blank:]]+- DTX aggregated epoch:[[:blank:]]+NA \\(NA\\)$"); + assert_nl_equal(dvt_fake_print_buffer, 5); opt.path = ""; dvt_fake_print_reset(); assert_success(ddb_run_dtx_stat(&ctx, &opt)); + cont_cnt = (DAOS_ON_VALGRIND) ? 8 : 10; + for (i = 0; i < cont_cnt; i++) { + char buf[] = "^DTX entries statistics of container " + "CONT:[[:blank:]]+\\(/\\[0\\]\\)[[:blank:]]+/[[:digit:]-]+$"; + + buf[59] += i; + assert_regex_match(dvt_fake_print_buffer, buf); + } + assert_regex_match(dvt_fake_print_buffer, + "^DTX entries statistics of the pool \\(null\\)$"); +} + +static uint64_t +dtx_get_cmt_time(char *buf) +{ + uint64_t cmt_time; + + buf = strstr(buf, "- Committed DTX time:"); + assert_non_null(buf); + buf = strstr(buf, "("); + assert_non_null(buf); + + buf++; + cmt_time = 0; + while (*buf >= '0' && *buf <= '9') { + cmt_time *= 10; + cmt_time += *buf - '0'; + buf++; + } + + return cmt_time; +} + +static void +dtx_aggr_tests(void **state) +{ + uuid_t *p_uuid = &g_uuids[3]; + struct dt_vos_pool_ctx *tctx = *state; + struct ddb_ctx ctx = {0}; + struct dtx_stat_options opt_stat = {0}; + struct dtx_aggr_options opt_aggr = {0}; + daos_handle_t coh; + char buf[256]; + + ctx.dc_poh = tctx->dvt_poh; + ctx.dc_io_ft.ddb_print_error = dvt_fake_print; + ctx.dc_io_ft.ddb_print_message = dvt_fake_print; + ctx.dc_write_mode = true; + + buf[0] = '/'; + uuid_unparse(*p_uuid, &buf[1]); + + /* Insert 8 mocked DTX entries */ + assert_success(vos_cont_open(tctx->dvt_poh, *p_uuid, &coh)); + dvt_vos_insert_dtx_records(coh, 10, 8); + assert_success(vos_cont_close(coh)); + + opt_stat.path = buf; + dvt_fake_print_reset(); + assert_success(ddb_run_dtx_stat(&ctx, &opt_stat)); + assert_regex_match(dvt_fake_print_buffer, + "^[[:blank:]]+- Committed DTX count:[[:blank:]]+8$"); + + /* Test aggregation without epoch (i.e. all 8 DTX entries) */ + opt_aggr.path = buf; + opt_aggr.format = DDB_DTX_AGGR_NOW; + assert_success(ddb_run_dtx_aggr(&ctx, &opt_aggr)); + + dvt_fake_print_reset(); + assert_success(ddb_run_dtx_stat(&ctx, &opt_stat)); + assert_regex_match(dvt_fake_print_buffer, + "^[[:blank:]]+- Committed DTX count:[[:blank:]]+0$"); assert_regex_match( dvt_fake_print_buffer, - "^[[:blank:]]+- Number of committed DTX of the container:[[:blank:]]+1$"); + "^[[:blank:]]+- DTX aggregated epoch:[[:blank:]]+.+ \\([[:digit:]]+\\)$"); + + /* Insert 10 mocked DTX entries */ + assert_success(vos_cont_open(tctx->dvt_poh, *p_uuid, &coh)); + dvt_vos_insert_dtx_records(coh, 10, 3); + sleep(2); + dvt_vos_insert_dtx_records(coh, 10, 7); + assert_success(vos_cont_close(coh)); + + opt_stat.details = true; + dvt_fake_print_reset(); + assert_success(ddb_run_dtx_stat(&ctx, &opt_stat)); assert_regex_match(dvt_fake_print_buffer, - "^[[:blank:]]+- DTX newest aggregated time:.+, 0$"); + "^[[:blank:]]+- Committed DTX count:[[:blank:]]+10$"); + + /* Test aggregation with an epoch (i.e. aggregate 3 first DTX entries) */ + opt_aggr.format = DDB_DTX_AGGR_CMT_TIME; + opt_aggr.cmt_time = dtx_get_cmt_time(dvt_fake_print_buffer) + 1; + assert_success(ddb_run_dtx_aggr(&ctx, &opt_aggr)); + + dvt_fake_print_reset(); + assert_success(ddb_run_dtx_stat(&ctx, &opt_stat)); + assert_regex_match(dvt_fake_print_buffer, + "^[[:blank:]]+- Committed DTX count:[[:blank:]]+7$"); + + /* Test aggregation without epoch (i.e. aggregate last 7 DTX entries) */ + opt_aggr.format = DDB_DTX_AGGR_NOW; + assert_success(ddb_run_dtx_aggr(&ctx, &opt_aggr)); + + dvt_fake_print_reset(); + assert_success(ddb_run_dtx_stat(&ctx, &opt_stat)); assert_regex_match(dvt_fake_print_buffer, - "^Number of committed DTX of the pool:[[:blank:]]+1$"); + "^[[:blank:]]+- Committed DTX count:[[:blank:]]+0$"); } /* @@ -497,6 +618,7 @@ ddb_commands_tests_run() TEST(dtx_act_discard_invalid_tests), TEST(dtx_abort_entry_tests), TEST(feature_cmd_tests), + TEST(dtx_aggr_tests), }; return cmocka_run_group_tests_name("DDB commands tests", tests, diff --git a/src/utils/ddb/tests/ddb_parse_tests.c b/src/utils/ddb/tests/ddb_parse_tests.c index fdc1b025927..bcb3efe9179 100644 --- a/src/utils/ddb/tests/ddb_parse_tests.c +++ b/src/utils/ddb/tests/ddb_parse_tests.c @@ -351,31 +351,41 @@ pool_flags_tests(void **state) } static void -date2epoch_tests(void **state) +date2cmt_time_tests(void **state) { - uint64_t epoch = -1; + uint64_t cmt_time; int rc; - rc = ddb_date2epoch(NULL, &epoch); + cmt_time = -1; + rc = ddb_date2cmt_time(NULL, &cmt_time); assert_rc_equal(rc, -DER_INVAL); - assert_int_equal(epoch, -1); - rc = ddb_date2epoch("1970-01-01 00:00:00", NULL); + assert_int_equal(cmt_time, -1); + + rc = ddb_date2cmt_time("1970-01-01 00:00:00", NULL); assert_rc_equal(rc, -DER_INVAL); - assert_int_equal(epoch, -1); - rc = ddb_date2epoch(NULL, NULL); + assert_int_equal(cmt_time, -1); + + rc = ddb_date2cmt_time(NULL, NULL); assert_rc_equal(rc, -DER_INVAL); - assert_int_equal(epoch, -1); - rc = ddb_date2epoch("foo", NULL); + assert_int_equal(cmt_time, -1); + + rc = ddb_date2cmt_time("foo", NULL); assert_rc_equal(rc, -DER_INVAL); - assert_int_equal(epoch, -1); - rc = ddb_date2epoch("0000-00-00 00:00:00", &epoch); + assert_int_equal(cmt_time, -1); + + rc = ddb_date2cmt_time("0000-00-00 00:00:00", &cmt_time); assert_rc_equal(rc, -DER_INVAL); - assert_int_equal(epoch, -1); + assert_int_equal(cmt_time, -1); + + rc = ddb_date2cmt_time("1970-01-01 00:00:00", &cmt_time); + assert_success(rc); + assert_int_equal(rc, 0); + assert_int_equal(cmt_time, 0ull); - rc = ddb_date2epoch("1970-01-01 00:00:00", &epoch); + rc = ddb_date2cmt_time("1970-01-01 00:01:00", &cmt_time); assert_success(rc); assert_int_equal(rc, 0); - assert_int_equal(epoch, 0x9aa0d62878400000ull); + assert_int_equal(cmt_time, 60ull); } /* @@ -390,7 +400,7 @@ ddb_parse_tests_run() static const struct CMUnitTest tests[] = { TEST(vos_file_parts_tests), TEST(string_to_argv_tests), TEST(parse_args_tests), TEST(parse_dtx_id_tests), TEST(keys_are_parsed_correctly), TEST(pool_flags_tests), - TEST(date2epoch_tests), + TEST(date2cmt_time_tests), }; return cmocka_run_group_tests_name("DDB helper parsing function tests", tests, NULL, NULL); diff --git a/src/utils/ddb/tests/ddb_test_driver.h b/src/utils/ddb/tests/ddb_test_driver.h index 4573a395e82..270f90d3a4c 100644 --- a/src/utils/ddb/tests/ddb_test_driver.h +++ b/src/utils/ddb/tests/ddb_test_driver.h @@ -78,7 +78,7 @@ int ddb_test_pool_setup(struct dt_vos_pool_ctx *tctx); extern uint32_t dvt_fake_print_called; extern bool dvt_fake_print_just_count; -#define DVT_FAKE_PRINT_BUFFER_SIZE (2048) +#define DVT_FAKE_PRINT_BUFFER_SIZE (0x20000) extern char dvt_fake_print_buffer[DVT_FAKE_PRINT_BUFFER_SIZE]; int dvt_fake_print(const char *fmt, ...); void dvt_fake_print_reset(void); diff --git a/src/vos/tests/vts_dtx.c b/src/vos/tests/vts_dtx.c index 944fdc0f744..12cd6d72728 100644 --- a/src/vos/tests/vts_dtx.c +++ b/src/vos/tests/vts_dtx.c @@ -800,7 +800,7 @@ dtx_18(void **state) daos_key_t akey; d_iov_t val_iov; uint64_t epoch; - uint32_t cnt; + uint64_t cnt; char dkey_buf[UPDATE_DKEY_SIZE]; char akey_buf[UPDATE_AKEY_SIZE]; char update_buf[UPDATE_BUF_SIZE]; @@ -842,15 +842,15 @@ dtx_18(void **state) sleep(3); - rc = vos_dtx_get_cmt_cnt(args->ctx.tc_co_hdl, &cnt); + rc = vos_dtx_get_cmt_stat(args->ctx.tc_co_hdl, &cnt, NULL); assert_rc_equal(rc, 0); assert_int_equal(cnt, 10); /* Aggregate the DTXs. */ - rc = vos_dtx_aggregate(args->ctx.tc_co_hdl); + rc = vos_dtx_aggregate(args->ctx.tc_co_hdl, NULL); assert_rc_equal(rc, 0); - rc = vos_dtx_get_cmt_cnt(args->ctx.tc_co_hdl, &cnt); + rc = vos_dtx_get_cmt_stat(args->ctx.tc_co_hdl, &cnt, NULL); assert_rc_equal(rc, 0); assert_int_equal(cnt, 0); diff --git a/src/vos/vos_dtx.c b/src/vos/vos_dtx.c index a64b8491765..f3cd477239b 100644 --- a/src/vos/vos_dtx.c +++ b/src/vos/vos_dtx.c @@ -13,6 +13,7 @@ #define D_LOGFAC DD_FAC(vos) #include +#include #include #include "vos_layout.h" #include "vos_internal.h" @@ -3052,134 +3053,201 @@ vos_dtx_set_flags(daos_handle_t coh, struct dtx_id dtis[], int count, uint32_t f return rc; } -int -vos_dtx_aggregate(daos_handle_t coh) +static int +dtx_blob_aggregate(struct umem_instance *umm, struct vos_tls *tls, struct vos_container *cont, + struct vos_cont_df *cont_df, umem_off_t dbd_off, const uint64_t *cmt_time) { - struct vos_tls *tls = vos_tls_get(false); - struct vos_container *cont; - struct vos_cont_df *cont_df; - struct umem_instance *umm; - struct vos_dtx_blob_df *dbd; - struct vos_dtx_blob_df *tmp; - uint64_t epoch; - umem_off_t dbd_off; - umem_off_t next = UMOFF_NULL; - int count = 0; - int rc; - int i; - - cont = vos_hdl2cont(coh); - D_ASSERT(cont != NULL); - - cont_df = cont->vc_cont_df; - dbd_off = cont_df->cd_dtx_committed_head; - umm = vos_cont2umm(cont); - epoch = cont_df->cd_newest_aggregated; + struct vos_dtx_blob_df *dbd; + umem_off_t dbd_next_off = UMOFF_NULL; + uint64_t epoch; + int dtx_aggr_count; + bool is_dbd_freed = false; + int i; + int rc; dbd = umem_off2ptr(umm, dbd_off); - if (dbd == NULL || dbd->dbd_count == 0) - return 0; - - D_ASSERT(cont->vc_pool->vp_sysdb == false); - /* Take the opportunity to free some memory if we can */ - lrua_array_aggregate(cont->vc_dtx_array); + if (dbd == NULL) { + rc = 0; + goto out; + } rc = umem_tx_begin(umm, NULL); - if (rc != 0) { - D_ERROR("Failed to TX begin for DTX aggregation "UMOFF_PF": " - DF_RC"\n", UMOFF_P(dbd_off), DP_RC(rc)); - return rc; + if (unlikely(rc != 0)) { + D_ERROR("Failed to TX begin for DTX aggregation " UMOFF_PF ": " DF_RC "\n", + UMOFF_P(dbd_off), DP_RC(rc)); + goto out; } + dtx_aggr_count = 0; + epoch = cont_df->cd_newest_aggregated; for (i = 0; i < dbd->dbd_count; i++) { - struct vos_dtx_cmt_ent_df *dce_df; - d_iov_t kiov; + struct vos_dtx_cmt_ent_df *dce_df; + d_iov_t kiov; dce_df = &dbd->dbd_committed_data[i]; - if (epoch < dce_df->dce_epoch) - epoch = dce_df->dce_epoch; + if (cmt_time != NULL && *cmt_time < dce_df->dce_cmt_time) + break; + d_iov_set(&kiov, &dce_df->dce_xid, sizeof(dce_df->dce_xid)); rc = dbtree_delete(cont->vc_dtx_committed_hdl, BTR_PROBE_EQ, &kiov, NULL); - if (rc == 0) { - count++; - } else if (rc != -DER_NONEXIST) { + if (rc == -DER_NONEXIST) + rc = 0; + if (unlikely(rc != 0)) { D_ERROR("Failed to remove entry for DTX aggregation " UMOFF_PF": "DF_RC"\n", UMOFF_P(dbd_off), DP_RC(rc)); - goto out; + goto out_tx_end; } + + dtx_aggr_count++; + if (epoch < dce_df->dce_epoch) + epoch = dce_df->dce_epoch; } if (epoch != cont_df->cd_newest_aggregated) { rc = umem_tx_add_ptr(umm, &cont_df->cd_newest_aggregated, sizeof(cont_df->cd_newest_aggregated)); - if (rc != 0) { - D_ERROR("Failed to refresh epoch for DTX aggregation "UMOFF_PF": "DF_RC"\n", + if (unlikely(rc != 0)) { + D_ERROR("Failed to refresh epoch for DTX aggregation " UMOFF_PF ": " DF_RC + "\n", UMOFF_P(dbd_off), DP_RC(rc)); - goto out; + goto out_tx_end; } - cont_df->cd_newest_aggregated = epoch; } - next = dbd->dbd_next; - tmp = umem_off2ptr(umm, next); - if (tmp == NULL) { - /* The last blob for committed DTX blob. */ - D_ASSERT(cont_df->cd_dtx_committed_tail == - cont_df->cd_dtx_committed_head); + if (dbd->dbd_count - dtx_aggr_count > 0) { + size_t buf_len; - rc = umem_tx_add_ptr(umm, &cont_df->cd_dtx_committed_tail, - sizeof(cont_df->cd_dtx_committed_tail)); - if (rc != 0) { - D_ERROR("Failed to update tail for DTX aggregation " - UMOFF_PF": "DF_RC"\n", + rc = umem_tx_add_ptr(umm, &dbd->dbd_committed_data[0], + sizeof(dbd->dbd_committed_data[0]) * dbd->dbd_count); + if (unlikely(rc != 0)) { + D_ERROR("Failed update committed DTX blob " UMOFF_PF ": " DF_RC "\n", UMOFF_P(dbd_off), DP_RC(rc)); - goto out; + goto out_tx_end; } + buf_len = (dbd->dbd_count - dtx_aggr_count) * sizeof(dbd->dbd_committed_data[0]); + memmove(&dbd->dbd_committed_data[0], &dbd->dbd_committed_data[dtx_aggr_count], + buf_len); - cont_df->cd_dtx_committed_tail = UMOFF_NULL; + rc = umem_tx_add_ptr(umm, &dbd->dbd_count, sizeof(dbd->dbd_count)); + if (unlikely(rc != 0)) { + D_ERROR("Failed update committed DTX count " UMOFF_PF ": " DF_RC "\n", + UMOFF_P(dbd_off), DP_RC(rc)); + goto out_tx_end; + } + dbd->dbd_count -= dtx_aggr_count; } else { - rc = umem_tx_add_ptr(umm, &tmp->dbd_prev, - sizeof(tmp->dbd_prev)); - if (rc != 0) { - D_ERROR("Failed to update prev for DTX aggregation " - UMOFF_PF": "DF_RC"\n", + struct vos_dtx_blob_df *dbd_next; + umem_off_t dbd_prev_off; + + dbd_next_off = dbd->dbd_next; + dbd_next = umem_off2ptr(umm, dbd_next_off); + dbd_prev_off = dbd->dbd_prev; + + D_ASSERT(UMOFF_IS_NULL(dbd_prev_off)); + D_ASSERT(dbd_off == cont_df->cd_dtx_committed_head); + rc = umem_tx_add_ptr(umm, &cont_df->cd_dtx_committed_head, + sizeof(cont_df->cd_dtx_committed_head)); + if (unlikely(rc != 0)) { + D_ERROR("Failed to update head for DTX aggregation " UMOFF_PF ": " DF_RC + "\n", UMOFF_P(dbd_off), DP_RC(rc)); - goto out; + goto out_tx_end; + } + cont_df->cd_dtx_committed_head = dbd_next_off; + + if (dbd_next == NULL) { + D_ASSERT(dbd_off == cont_df->cd_dtx_committed_tail); + rc = umem_tx_add_ptr(umm, &cont_df->cd_dtx_committed_tail, + sizeof(cont_df->cd_dtx_committed_tail)); + if (unlikely(rc != 0)) { + D_ERROR("Failed to update tail for DTX aggregation " UMOFF_PF + ": " DF_RC "\n", + UMOFF_P(dbd_off), DP_RC(rc)); + goto out_tx_end; + } + cont_df->cd_dtx_committed_tail = dbd_prev_off; + } else { + rc = umem_tx_add_ptr(umm, &dbd_next->dbd_prev, sizeof(dbd_next->dbd_prev)); + if (unlikely(rc != 0)) { + D_ERROR("Failed to update previous DTXs blob for DTX " + "aggregation " UMOFF_PF ": " DF_RC "\n", + UMOFF_P(dbd_off), DP_RC(rc)); + goto out_tx_end; + } + dbd_next->dbd_prev = dbd_prev_off; } - tmp->dbd_prev = UMOFF_NULL; + rc = umem_free(umm, dbd_off); + if (unlikely(rc != 0)) { + D_ERROR("Failed to free DTXs blob " UMOFF_PF ": " DF_RC "\n", + UMOFF_P(dbd_off), DP_RC(rc)); + goto out_tx_end; + } + is_dbd_freed = true; } - rc = umem_tx_add_ptr(umm, &cont_df->cd_dtx_committed_head, - sizeof(cont_df->cd_dtx_committed_head)); - if (rc != 0) { - D_ERROR("Failed to update head for DTX aggregation " - UMOFF_PF": "DF_RC"\n", - UMOFF_P(dbd_off), DP_RC(rc)); +out_tx_end: + rc = umem_tx_end(umm, rc); + if (likely(rc != 0)) { + DL_ERROR(rc, + "Failed to commit DTX commit aggregation of blob %p (" UMOFF_PF + ") of cont " DF_UUID, + dbd, UMOFF_P(dbd_off), DP_UUID(cont->vc_id)); goto out; } - cont_df->cd_dtx_committed_head = next; + if (dtx_aggr_count > 0) { + cont->vc_dtx_committed_count -= dtx_aggr_count; + cont->vc_pool->vp_dtx_committed_count -= dtx_aggr_count; + d_tm_dec_gauge(tls->vtl_committed, dtx_aggr_count); - rc = umem_free(umm, dbd_off); + D_DEBUG(DB_IO, + "Release %i DTX committed entries of blob %p (" UMOFF_PF + ") of cont " DF_UUID, + dtx_aggr_count, dbd, UMOFF_P(dbd_off), DP_UUID(cont->vc_id)); + } + + if (is_dbd_freed) { + cont->vc_cmt_dtx_reindex_pos = dbd_next_off; + D_DEBUG(DB_IO, + "Removed blob of DTX committed entries %p (" UMOFF_PF ") of cont " DF_UUID, + dbd, UMOFF_P(dbd_off), DP_UUID(cont->vc_id)); + rc = 1; + } out: - rc = umem_tx_end(umm, rc); - if (rc == 0) { - if (cont->vc_cmt_dtx_reindex_pos == dbd_off) - cont->vc_cmt_dtx_reindex_pos = next; + return rc; +} - cont->vc_dtx_committed_count -= count; - cont->vc_pool->vp_dtx_committed_count -= count; - d_tm_dec_gauge(tls->vtl_committed, count); - } +int +vos_dtx_aggregate(daos_handle_t coh, const uint64_t *cmt_time) +{ + struct vos_container *cont; + struct vos_tls *tls; + struct umem_instance *umm; + struct vos_cont_df *cont_df; + umem_off_t dbd_off; + int rc; - DL_CDEBUG(rc != 0, DLOG_ERR, DB_IO, rc, - "Release DTX committed blob %p (" UMOFF_PF ") for cont " DF_UUID, dbd, - UMOFF_P(dbd_off), DP_UUID(cont->vc_id)); + tls = vos_tls_get(false); + D_ASSERT(tls != NULL); + + cont = vos_hdl2cont(coh); + D_ASSERT(cont != NULL); + D_ASSERT(cont->vc_pool->vp_sysdb == false); + + umm = vos_cont2umm(cont); + cont_df = cont->vc_cont_df; + dbd_off = cont_df->cd_dtx_committed_head; + + /* Take the opportunity to free some memory if we can */ + lrua_array_aggregate(cont->vc_dtx_array); + rc = dtx_blob_aggregate(umm, tls, cont, cont_df, dbd_off, cmt_time); + if (rc == 1 && UMOFF_IS_NULL(cont_df->cd_dtx_committed_head)) + rc = 0; return rc; } @@ -4010,13 +4078,20 @@ vos_dtx_local_end(struct dtx_handle *dth, int result) return result; } +struct dtx_time_stat_priv { + struct dtx_time_stat dts_pub; + /* DAOS-17322: Use of floating point to avoid integer overflow issue */ + long double dts_mean[2]; +}; + int -vos_dtx_get_cmt_cnt(daos_handle_t coh, uint32_t *cnt) +vos_dtx_get_cmt_stat(daos_handle_t coh, uint64_t *cmt_cnt, struct dtx_time_stat *dts) { struct umem_instance *umm; struct vos_container *cont; struct vos_dtx_blob_df *dbd; - uint32_t tmp; + uint64_t cmt_cnt_tmp; + struct dtx_time_stat_priv dts_tmp = {0}; int rc; cont = vos_hdl2cont(coh); @@ -4024,14 +4099,16 @@ vos_dtx_get_cmt_cnt(daos_handle_t coh, uint32_t *cnt) rc = -DER_INVAL; goto out; } - if (cnt == NULL) { + if (cmt_cnt == NULL) { rc = -DER_INVAL; goto out; } - tmp = 0; + cmt_cnt_tmp = 0; umm = vos_cont2umm(cont); dbd = umem_off2ptr(umm, cont->vc_cont_df->cd_dtx_committed_head); + dts_tmp.dts_pub.dts_epoch[0] = DAOS_EPOCH_MAX; + dts_tmp.dts_pub.dts_cmt_time[0] = UINT64_MAX; while (dbd != NULL) { if (dbd->dbd_magic != DTX_CMT_BLOB_MAGIC) { D_ERROR("Committed DTX blob with bad magic: container=" DF_UUID @@ -4040,12 +4117,47 @@ vos_dtx_get_cmt_cnt(daos_handle_t coh, uint32_t *cnt) rc = -DER_INVAL; goto out; } - tmp += dbd->dbd_count; + + cmt_cnt_tmp += dbd->dbd_count; + if (dts != NULL) { + int i; + + for (i = 0; i < dbd->dbd_count; i++) { + struct vos_dtx_cmt_ent_df *dce_df; + + dce_df = &dbd->dbd_committed_data[i]; + + if (dts_tmp.dts_pub.dts_epoch[0] > dce_df->dce_epoch) + dts_tmp.dts_pub.dts_epoch[0] = dce_df->dce_epoch; + if (dts_tmp.dts_pub.dts_epoch[1] < dce_df->dce_epoch) + dts_tmp.dts_pub.dts_epoch[1] = dce_df->dce_epoch; + dts_tmp.dts_mean[0] += dce_df->dce_epoch; + + if (dts_tmp.dts_pub.dts_cmt_time[0] > dce_df->dce_cmt_time) + dts_tmp.dts_pub.dts_cmt_time[0] = dce_df->dce_cmt_time; + if (dts_tmp.dts_pub.dts_cmt_time[1] < dce_df->dce_cmt_time) + dts_tmp.dts_pub.dts_cmt_time[1] = dce_df->dce_cmt_time; + dts_tmp.dts_mean[1] += dce_df->dce_cmt_time; + } + } dbd = umem_off2ptr(umm, dbd->dbd_next); } - *cnt = tmp; + *cmt_cnt = cmt_cnt_tmp; + + if (dts != NULL) { + if (cmt_cnt_tmp != 0) { + dts_tmp.dts_mean[0] /= (long double)cmt_cnt_tmp; + dts_tmp.dts_pub.dts_epoch[2] = (daos_epoch_t)dts_tmp.dts_mean[0]; + + dts_tmp.dts_mean[1] /= (long double)cmt_cnt_tmp; + dts_tmp.dts_pub.dts_cmt_time[2] = (uint64_t)dts_tmp.dts_mean[1]; + } + + memcpy(dts, &dts_tmp, sizeof(struct dtx_time_stat)); + } + rc = 0; out: