diff --git a/c_src/bdberl_drv.c b/c_src/bdberl_drv.c index f14ca4f..8f45482 100644 --- a/c_src/bdberl_drv.c +++ b/c_src/bdberl_drv.c @@ -39,6 +39,7 @@ static void do_async_lock_stat(void* arg); static void do_async_log_stat(void* arg); static void do_async_memp_stat(void* arg); static void do_async_mutex_stat(void* arg); +static void do_async_txn_stat(void* arg); static int add_dbref(PortData* data, int dbref); static int del_dbref(PortData* data, int dbref); @@ -1006,6 +1007,46 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd, int rc = G_DB_ENV->mutex_stat_print(G_DB_ENV, flags); RETURN_INT(rc, outbuf); } + case CMD_TXN_STAT: + { + FAIL_IF_ASYNC_PENDING(d, outbuf); + + // Inbuf is <> + // If the working buffer is large enough, copy the data to put/get into it. Otherwise, realloc + // until it is large enough + if (d->work_buffer_sz < inbuf_sz) + { + d->work_buffer = driver_realloc(d->work_buffer, inbuf_sz); + d->work_buffer_sz = inbuf_sz; + } + + // Copy the payload into place + memcpy(d->work_buffer, inbuf, inbuf_sz); + d->work_buffer_offset = inbuf_sz; + + // Mark the port as busy and then schedule the appropriate async operation + d->async_op = cmd; + d->async_pool = G_TPOOL_GENERAL; + bdberl_tpool_run(d->async_pool, &do_async_txn_stat, d, 0, &d->async_job); + + // Let caller know that the operation is in progress + // Outbuf is: <<0:32>> + RETURN_INT(0, outbuf); + } + case CMD_TXN_STAT_PRINT: + { + FAIL_IF_ASYNC_PENDING(d, outbuf); + + // Inbuf is << Flags:32 >> + unsigned int flags = UNPACK_INT(inbuf, 0); + + // Outbuf is <> + // Run the command on the VM thread - this is for debugging only, + // any real monitoring will use the async lock_stat + int rc = G_DB_ENV->txn_stat_print(G_DB_ENV, flags); + RETURN_INT(rc, outbuf); + } + } *outbuf = 0; return 0; @@ -1711,6 +1752,127 @@ static void async_cleanup_and_send_mutex_stats(PortData* d, DB_MUTEX_STAT *msp) driver_send_term(port, pid, response, sizeof(response) / sizeof(response[0])); } +#define STATS_TUPLE(base, member) \ + ERL_DRV_ATOM, driver_mk_atom(#member), \ + ERL_DRV_UINT, (base)->member, \ + ERL_DRV_TUPLE, 2 + +#define STATS_LSN_TUPLE(base, member) \ + ERL_DRV_ATOM, driver_mk_atom(#member), \ + ERL_DRV_UINT, (base)->member.file, \ + ERL_DRV_UINT, (base)->member.offset, \ + ERL_DRV_TUPLE, 2, \ + ERL_DRV_TUPLE, 2 + +static void send_txn_tstat(ErlDrvPort port, ErlDrvTermData pid, DB_TXN_ACTIVE *tasp) +{ + char *name = tasp->name ? tasp->name : ""; + int name_len = strlen(name); + char tid_str[32]; + char *status_str; + switch (tasp->status) + { + case TXN_ABORTED: + status_str = "aborted"; + break; + case TXN_COMMITTED: + status_str = "committed"; + break; + case TXN_PREPARED: + status_str = "prepared"; + break; + case TXN_RUNNING: + status_str = "running"; + break; + default: + status_str = "undefined"; + break; + } + + int tid_str_len = snprintf(tid_str, sizeof(tid_str), "%lu", (unsigned long) tasp->tid); + + ErlDrvTermData response[] = { + ERL_DRV_ATOM, driver_mk_atom("txn"), + STATS_TUPLE(tasp, txnid), /* Transaction ID */ + STATS_TUPLE(tasp, parentid), /* Transaction ID of parent */ + STATS_TUPLE(tasp, pid), /* Process owning txn ID - pid_t */ + ERL_DRV_ATOM, driver_mk_atom("tid"),/* OSX has 32-bit ints in erlang, so return as */ + ERL_DRV_STRING, (ErlDrvTermData) tid_str, tid_str_len, /* a string */ + ERL_DRV_TUPLE, 2, + STATS_LSN_TUPLE(tasp, lsn), /* LSN when transaction began */ + STATS_LSN_TUPLE(tasp, read_lsn), /* Read LSN for MVCC */ + STATS_TUPLE(tasp, mvcc_ref), /* MVCC reference count */ + + // Start of list + ERL_DRV_ATOM, driver_mk_atom("status"), + ERL_DRV_ATOM, driver_mk_atom(status_str), + ERL_DRV_TUPLE, 2, + + ERL_DRV_ATOM, driver_mk_atom("name"), + ERL_DRV_STRING, (ErlDrvTermData) name, name_len, + ERL_DRV_TUPLE, 2, + + + // End of list + ERL_DRV_NIL, + ERL_DRV_LIST, 9+1, + ERL_DRV_TUPLE, 2 + }; + driver_send_term(port, pid, response, sizeof(response) / sizeof(response[0])); +} + +#define ST_STATS_LSN_TUPLE(base, member) \ + ERL_DRV_ATOM, driver_mk_atom(#member), \ + ERL_DRV_UINT, (base)->st_##member.file, \ + ERL_DRV_UINT, (base)->st_##member.offset, \ + ERL_DRV_TUPLE, 2, \ + ERL_DRV_TUPLE, 2 + +static void async_cleanup_and_send_txn_stats(PortData* d, DB_TXN_STAT *tsp) +{ + // Save the port and pid references -- we need copies independent from the PortData + // structure. Once we release the port_lock after clearing the cmd, it's possible that + // the port could go away without waiting on us to finish. This is acceptable, but we need + // to be certain that there is no overlap of data between the two threads. driver_send_term + // is safe to use from a thread, even if the port you're sending from has already expired. + ErlDrvPort port = d->port; + ErlDrvTermData pid = d->port_owner; + async_cleanup(d); + + // First send the array of active transactions */ + int i; + for (i = 0; i < tsp->st_nactive; i++) + { + send_txn_tstat(port, pid, tsp->st_txnarray+i); + } + + // Then send the global stats + ErlDrvTermData response[] = { + ERL_DRV_ATOM, driver_mk_atom("ok"), + // Start of list + ST_STATS_TUPLE(tsp, nrestores), /* number of restored transactions + after recovery. */ + ST_STATS_LSN_TUPLE(tsp, last_ckp), /* lsn of the last checkpoint */ + ST_STATS_TUPLE(tsp, time_ckp), /* time of last checkpoint (time_t to uint) */ + ST_STATS_TUPLE(tsp, last_txnid), /* last transaction id given out */ + ST_STATS_TUPLE(tsp, maxtxns), /* maximum txns possible */ + ST_STATS_TUPLE(tsp, naborts), /* number of aborted transactions */ + ST_STATS_TUPLE(tsp, nbegins), /* number of begun transactions */ + ST_STATS_TUPLE(tsp, ncommits), /* number of committed transactions */ + ST_STATS_TUPLE(tsp, nactive), /* number of active transactions */ + ST_STATS_TUPLE(tsp, nsnapshot), /* number of snapshot transactions */ + ST_STATS_TUPLE(tsp, maxnactive), /* maximum active transactions */ + ST_STATS_TUPLE(tsp, maxnsnapshot), /* maximum snapshot transactions */ + ST_STATS_TUPLE(tsp, region_wait), /* Region lock granted after wait. */ + ST_STATS_TUPLE(tsp, region_nowait), /* Region lock granted without wait. */ + ST_STATS_TUPLE(tsp, regsize), /* Region size. */ + // End of list + ERL_DRV_NIL, + ERL_DRV_LIST, 15+1, + ERL_DRV_TUPLE, 2 + }; + driver_send_term(port, pid, response, sizeof(response) / sizeof(response[0])); +} static void do_async_put(void* arg) { @@ -2090,6 +2252,33 @@ static void do_async_mutex_stat(void* arg) } } + +static void do_async_txn_stat(void* arg) +{ + // Payload is: <> + PortData* d = (PortData*)arg; + + // Extract operation flags + unsigned flags = UNPACK_INT(d->work_buffer, 0); + + DB_TXN_STAT *tsp = NULL; + int rc = G_DB_ENV->txn_stat(G_DB_ENV, &tsp, flags); + if (rc != 0 || tsp == NULL) + { + async_cleanup_and_send_rc(d, rc); + } + else + { + async_cleanup_and_send_txn_stats(d, tsp); + } + + // Finally, clean up stats + if (NULL != tsp) + { + free(tsp); + } +} + static void* zalloc(unsigned int size) { void* res = driver_alloc(size); diff --git a/c_src/bdberl_drv.h b/c_src/bdberl_drv.h index b30bc5b..e22d60e 100644 --- a/c_src/bdberl_drv.h +++ b/c_src/bdberl_drv.h @@ -57,6 +57,8 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd, #define CMD_MEMP_STAT_PRINT 26 #define CMD_MUTEX_STAT 27 #define CMD_MUTEX_STAT_PRINT 28 +#define CMD_TXN_STAT 29 +#define CMD_TXN_STAT_PRINT 30 /** * Command status values diff --git a/c_src/buildlib.sh b/c_src/buildlib.sh index 635f36e..03f723f 100755 --- a/c_src/buildlib.sh +++ b/c_src/buildlib.sh @@ -21,7 +21,7 @@ rm -rf system db-${DB_VER} tar -xzf db-${DB_VER}.tar.gz && \ ##(cd db-${DB_VER} && patch -p0 < ../bdb-align.patch ) && \ (cd db-${DB_VER}/build_unix && \ - ../dist/configure --prefix=$WORKDIR --disable-replication --disable-shared --with-pic && make && ranlib libdb-*.a && make install) && \ + ../dist/configure --prefix=$WORKDIR --enable-debug --disable-replication --disable-shared --with-pic && make && ranlib libdb-*.a && make install) && \ rm -rf db-${DB_VER} diff --git a/include/bdberl.hrl b/include/bdberl.hrl index c4cee89..21f2cc1 100644 --- a/include/bdberl.hrl +++ b/include/bdberl.hrl @@ -34,6 +34,8 @@ -define(CMD_MEMP_STAT_PRINT, 26). -define(CMD_MUTEX_STAT, 27). -define(CMD_MUTEX_STAT_PRINT,28). +-define(CMD_TXN_STAT, 29). +-define(CMD_TXN_STAT_PRINT, 30). -define(DB_TYPE_BTREE, 1). -define(DB_TYPE_HASH, 2). diff --git a/src/bdberl.erl b/src/bdberl.erl index 84fb557..98b1365 100644 --- a/src/bdberl.erl +++ b/src/bdberl.erl @@ -24,6 +24,8 @@ memp_stat_print/1, mutex_stat/1, mutex_stat_print/1, + txn_stat/1, + txn_stat_print/1, env_stat_print/1, transaction/1, transaction/2, transaction/3, put/3, put/4, @@ -1687,6 +1689,72 @@ mutex_stat_print(Opts) -> Error -> {error, Error} end. +%%-------------------------------------------------------------------- +%% @doc +%% Retrieve transaction stats +%% +%% This function retrieves transaction statistics +%% +%% === Options === +%% +%%
+%%
stat_clear
+%%
Reset statistics after returning their values
+%%
+%% +%% @spec txn_stat(Opts) -> {ok, [{atom(), number()}]} | {error, Error} +%% where +%% Opts = [atom()] +%% +%% @end +%%-------------------------------------------------------------------- +-spec txn_stat(Opts :: db_flags()) -> + {ok, [{atom(), number()}], [any()]} | db_error(). + +txn_stat(Opts) -> + Flags = process_flags(Opts), + Cmd = <>, + <> = erlang:port_control(get_port(), ?CMD_TXN_STAT, Cmd), + case decode_rc(Result) of + ok -> + recv_txn_stat([]); + Error -> + {error, Error} + end. + + +%%-------------------------------------------------------------------- +%% @doc +%% Print transaction stats +%% +%% This function prints transaction statistics to wherever +%% BDB messages are being sent +%% +%% === Options === +%% +%%
+%%
stat_all
+%%
Display all available information.
+%%
stat_clear
+%%
Reset statistics after displaying their values.
+%%
+%% +%% @spec txn_stat_print(Opts) -> ok | {error, Error} +%% where +%% Opts = [atom()] +%% +%% @end +%%-------------------------------------------------------------------- +-spec txn_stat_print(Opts :: db_flags()) -> + ok | db_error(). +txn_stat_print(Opts) -> + Flags = process_flags(Opts), + Cmd = <>, + <> = erlang:port_control(get_port(), ?CMD_TXN_STAT_PRINT, Cmd), + case decode_rc(Result) of + ok -> ok; + Error -> {error, Error} + end. %%-------------------------------------------------------------------- %% @doc @@ -1926,3 +1994,16 @@ recv_memp_stat(Fstats) -> {ok, Stats} -> {ok, Stats, Fstats} end. + +%% +%% Receive transaction stats +%% +recv_txn_stat(Tstats) -> + receive + {error, Reason} -> + {error, decode_rc(Reason)}; + {txn, Tstat} -> + recv_txn_stat([Tstat|Tstats]); + {ok, Stats} -> + {ok, Stats, Tstats} + end. diff --git a/test/bdberl_SUITE.erl b/test/bdberl_SUITE.erl index 55e9d5d..57caf4f 100644 --- a/test/bdberl_SUITE.erl +++ b/test/bdberl_SUITE.erl @@ -38,7 +38,8 @@ all() -> lock_stat_should_report_on_success, log_stat_should_report_on_success, memp_stat_should_report_on_success, - mutex_stat_should_report_on_success]. + mutex_stat_should_report_on_success, + txn_stat_should_report_on_success]. dbconfig(Config) -> @@ -303,3 +304,11 @@ memp_stat_should_report_on_success(_Config) -> mutex_stat_should_report_on_success(_Config) -> {ok, _Stat} = bdberl:mutex_stat([]), done. + +txn_stat_should_report_on_success(_Config) -> + {ok, _GStat1, []} = bdberl:txn_stat([]), + bdberl:txn_begin(), + {ok, _GStat2, [_ATxnStat]} = bdberl:txn_stat([]), + bdberl:txn_abort(), + {ok, _GStat3, []} = bdberl:txn_stat([]), + done.