diff --git a/c_src/bdberl_drv.c b/c_src/bdberl_drv.c index 11757a9..e84f2b6 100644 --- a/c_src/bdberl_drv.c +++ b/c_src/bdberl_drv.c @@ -35,6 +35,7 @@ static void do_async_txnop(void* arg); static void do_async_cursor_get(void* arg); static void do_async_truncate(void* arg); static void do_async_stat(void* arg); +static void do_async_lock_stat(void* arg); static int add_dbref(PortData* data, int dbref); static int del_dbref(PortData* data, int dbref); @@ -846,6 +847,45 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd, int rc = G_DB_ENV->stat_print(G_DB_ENV, flags); RETURN_INT(rc, outbuf); } + case CMD_LOCK_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_lock_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_LOCK_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->lock_stat_print(G_DB_ENV, flags); + RETURN_INT(rc, outbuf); + } } *outbuf = 0; return 0; @@ -1302,6 +1342,81 @@ static void async_cleanup_and_send_queue_stats(PortData* d, DB_QUEUE_STAT *qsp) #undef QUEUE_STATS_TUPLE #endif // ENABLE_QUEUE +#define ST_STATS_TUPLE(base, member) \ + ERL_DRV_ATOM, driver_mk_atom(#member), \ + ERL_DRV_UINT, (base)->st_##member, \ + ERL_DRV_TUPLE, 2 + +#define ST_STATS_INT_TUPLE(base, member) \ + ERL_DRV_ATOM, driver_mk_atom(#member), \ + ERL_DRV_INT, (base)->st_##member, \ + ERL_DRV_TUPLE, 2 + +static void async_cleanup_and_send_lock_stats(PortData* d, DB_LOCK_STAT *lsp) +{ + // 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); + + ErlDrvTermData response[] = { + ERL_DRV_ATOM, driver_mk_atom("ok"), + // Start of list + ST_STATS_TUPLE(lsp, id), /* Last allocated locker ID. */ + ST_STATS_TUPLE(lsp, cur_maxid), /* Current maximum unused ID. */ + ST_STATS_TUPLE(lsp, maxlocks), /* Maximum number of locks in table. */ + ST_STATS_TUPLE(lsp, maxlockers), /* Maximum num of lockers in table. */ + ST_STATS_TUPLE(lsp, maxobjects), /* Maximum num of objects in table. */ + ST_STATS_TUPLE(lsp, partitions), /* number of partitions. */ + ST_STATS_INT_TUPLE(lsp, nmodes), /* Number of lock modes. */ + ST_STATS_TUPLE(lsp, nlockers), /* Current number of lockers. */ + ST_STATS_TUPLE(lsp, nlocks), /* Current number of locks. */ + ST_STATS_TUPLE(lsp, maxnlocks), /* Maximum number of locks so far. */ + ST_STATS_TUPLE(lsp, maxhlocks), /* Maximum number of locks in any bucket. */ + ST_STATS_TUPLE(lsp, locksteals), /* Number of lock steals so far. */ + ST_STATS_TUPLE(lsp, maxlsteals), /* Maximum number steals in any partition. */ + ST_STATS_TUPLE(lsp, maxnlockers), /* Maximum number of lockers so far. */ + ST_STATS_TUPLE(lsp, nobjects), /* Current number of objects. */ + ST_STATS_TUPLE(lsp, maxnobjects), /* Maximum number of objects so far. */ + ST_STATS_TUPLE(lsp, maxhobjects), /* Maximum number of objectsin any bucket. */ + ST_STATS_TUPLE(lsp, objectsteals), /* Number of objects steals so far. */ + ST_STATS_TUPLE(lsp, maxosteals), /* Maximum number of steals in any partition. */ + ST_STATS_TUPLE(lsp, nrequests), /* Number of lock gets. */ + ST_STATS_TUPLE(lsp, nreleases), /* Number of lock puts. */ + ST_STATS_TUPLE(lsp, nupgrade), /* Number of lock upgrades. */ + ST_STATS_TUPLE(lsp, ndowngrade), /* Number of lock downgrades. */ + ST_STATS_TUPLE(lsp, lock_wait), /* Lock conflicts w/ subsequent wait */ + ST_STATS_TUPLE(lsp, lock_nowait), /* Lock conflicts w/o subsequent wait */ + ST_STATS_TUPLE(lsp, ndeadlocks), /* Number of lock deadlocks. */ + ST_STATS_TUPLE(lsp, locktimeout), /* Lock timeout. */ + ST_STATS_TUPLE(lsp, nlocktimeouts), /* Number of lock timeouts. */ + ST_STATS_TUPLE(lsp, txntimeout), /* Transaction timeout. */ + ST_STATS_TUPLE(lsp, ntxntimeouts), /* Number of transaction timeouts. */ + ST_STATS_TUPLE(lsp, part_wait), /* Partition lock granted after wait. */ + ST_STATS_TUPLE(lsp, part_nowait), /* Partition lock granted without wait. */ + ST_STATS_TUPLE(lsp, part_max_wait), /* Max partition lock granted after wait. */ + ST_STATS_TUPLE(lsp, part_max_nowait), /* Max partition lock granted without wait. */ + ST_STATS_TUPLE(lsp, objs_wait), /* Object lock granted after wait. */ + ST_STATS_TUPLE(lsp, objs_nowait), /* Object lock granted without wait. */ + ST_STATS_TUPLE(lsp, lockers_wait), /* Locker lock granted after wait. */ + ST_STATS_TUPLE(lsp, lockers_nowait),/* Locker lock granted without wait. */ + ST_STATS_TUPLE(lsp, region_wait), /* Region lock granted after wait. */ + ST_STATS_TUPLE(lsp, region_nowait), /* Region lock granted without wait. */ + ST_STATS_TUPLE(lsp, hash_len), /* Max length of bucket. */ + ST_STATS_TUPLE(lsp, regsize), /* Region size. - will have to cast to uint */ + + // End of list + ERL_DRV_NIL, + ERL_DRV_LIST, 42+1, + ERL_DRV_TUPLE, 2 + }; + driver_send_term(port, pid, response, sizeof(response) / sizeof(response[0])); +} + static void do_async_put(void* arg) { // Payload is: <> @@ -1540,7 +1655,7 @@ static void do_async_stat(void* arg) rc = db->stat(db, d->txn, &sp, flags); if (rc != 0 || sp == NULL) { - async_cleanup_and_send_rc(d, ERROR_INVALID_DB_TYPE); + async_cleanup_and_send_rc(d, rc); } else { @@ -1571,6 +1686,32 @@ static void do_async_stat(void* arg) } } +static void do_async_lock_stat(void* arg) +{ + // Payload is: <> + PortData* d = (PortData*)arg; + + // Extract operation flags + unsigned flags = UNPACK_INT(d->work_buffer, 0); + + DB_LOCK_STAT *lsp = NULL; + int rc = G_DB_ENV->lock_stat(G_DB_ENV, &lsp, flags); + if (rc != 0 || lsp == NULL) + { + async_cleanup_and_send_rc(d, rc); + } + else + { + async_cleanup_and_send_lock_stats(d, lsp); + } + + // Finally, clean up lock stats + if (NULL != lsp) + { + free(lsp); + } +} + static void* zalloc(unsigned int size) { diff --git a/c_src/bdberl_drv.h b/c_src/bdberl_drv.h index c30eaf0..ddb89d8 100644 --- a/c_src/bdberl_drv.h +++ b/c_src/bdberl_drv.h @@ -49,6 +49,8 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd, #define CMD_DB_STAT 18 #define CMD_DB_STAT_PRINT 19 #define CMD_ENV_STAT_PRINT 20 +#define CMD_LOCK_STAT 21 +#define CMD_LOCK_STAT_PRINT 22 /** * Command status values diff --git a/include/bdberl.hrl b/include/bdberl.hrl index 4fdec9f..92aa674 100644 --- a/include/bdberl.hrl +++ b/include/bdberl.hrl @@ -26,6 +26,8 @@ -define(CMD_DB_STAT, 18). -define(CMD_DB_STAT_PRINT, 19). -define(CMD_ENV_STAT_PRINT, 20). +-define(CMD_LOCK_STAT, 21). +-define(CMD_LOCK_STAT_PRINT, 22). -define(DB_TYPE_BTREE, 1). -define(DB_TYPE_HASH, 2). diff --git a/src/bdberl.erl b/src/bdberl.erl index cc2184a..e2127be 100644 --- a/src/bdberl.erl +++ b/src/bdberl.erl @@ -15,7 +15,10 @@ get_data_dirs/0, get_txn_timeout/0, stat/2, - stat_print/1, stat_print/2, + stat_print/2, + lock_stat/1, + lock_stat_print/1, + env_stat_print/1, transaction/1, transaction/2, transaction/3, put/3, put/4, put_r/3, put_r/4, @@ -1286,7 +1289,7 @@ get_txn_timeout() -> %% @doc %% Retrieve database stats %% -%% This function retrieves performance statistics from the database. +%% This function retrieves database statistics %% %% === Options === %% @@ -1336,11 +1339,12 @@ stat(Db, Opts) -> end. + %%-------------------------------------------------------------------- %% @doc %% Print database stats %% -%% This function prints performance statistics from the database to wherever +%% This function prints statistics from the database to wherever %% BDB messages are being sent %% %% === Options === @@ -1382,6 +1386,87 @@ stat_print(Db, Opts) -> Error -> {error, Error} end. +%%-------------------------------------------------------------------- +%% @doc +%% Retrieve lock stats +%% +%% This function retrieves lock statistics from the database. +%% +%% === Options === +%% +%%
+%%
stat_clear
+%%
Reset statistics after returning their values
+%%
+%% +%% @spec stat(Opts) -> {ok, [{atom(), number()}]} | {error, Error} +%% where +%% Opts = [atom()] +%% +%% @end +%%-------------------------------------------------------------------- +-spec lock_stat(Opts :: db_flags()) -> + {ok, [{atom(), number()}]} | db_error(). + +lock_stat(Opts) -> + Flags = process_flags(Opts), + Cmd = <>, + <> = erlang:port_control(get_port(), ?CMD_LOCK_STAT, Cmd), + case decode_rc(Result) of + ok -> + receive + {error, Reason} -> + {error, decode_rc(Reason)}; + {ok, Stats} -> + {ok, Stats} + end; + Error -> + {error, Error} + end. + + + +%%-------------------------------------------------------------------- +%% @doc +%% Print lock stats +%% +%% This function prints lock statistics to wherever +%% BDB messages are being sent +%% +%% === Options === +%% +%%
+%%
stat_all
+%%
Display all available information.
+%%
stat_clear
+%%
Reset statistics after displaying their values.
+%%
stat_lock_conf
+%%
Display the lock conflict matrix.
+%%
stat_lock_lockers
+%%
Display the lockers within hash chains.
+%%
stat_lock_objects
+%%
Display the lock objects within hash chains.
+%%
stat_lock_params
+%%
Display the locking subsystem parameters.
+%%
+%% +%% @spec lock_stat_print(Opts) -> ok | {error, Error} +%% where +%% Opts = [atom()] +%% +%% @end +%%-------------------------------------------------------------------- +-spec lock_stat_print(Opts :: db_flags()) -> + ok | db_error(). +lock_stat_print(Opts) -> + Flags = process_flags(Opts), + Cmd = <>, + <> = erlang:port_control(get_port(), ?CMD_LOCK_STAT_PRINT, Cmd), + case decode_rc(Result) of + ok -> ok; + Error -> {error, Error} + end. + %%-------------------------------------------------------------------- %% @doc @@ -1409,9 +1494,9 @@ stat_print(Db, Opts) -> %% %% @end %%-------------------------------------------------------------------- --spec stat_print(Opts :: db_flags()) -> +-spec env_stat_print(Opts :: db_flags()) -> ok | db_error(). -stat_print(Opts) -> +env_stat_print(Opts) -> Flags = process_flags(Opts), Cmd = <>, <> = erlang:port_control(get_port(), ?CMD_ENV_STAT_PRINT, Cmd), @@ -1537,6 +1622,10 @@ flag_value(Flag) -> set_recno -> ?DB_SET_RECNO; stat_all -> ?DB_STAT_ALL; stat_clear -> ?DB_STAT_CLEAR; + stat_lock_conf -> ?DB_STAT_LOCK_CONF; + stat_lock_lockers -> ?DB_STAT_LOCK_LOCKERS; + stat_lock_objects -> ?DB_STAT_LOCK_OBJECTS; + stat_lock_params -> ?DB_STAT_LOCK_PARAMS; stat_subsystem -> ?DB_STAT_SUBSYSTEM; threaded -> ?DB_THREAD; truncate -> ?DB_TRUNCATE; diff --git a/test/bdberl_SUITE.erl b/test/bdberl_SUITE.erl index a9a2dc9..53ab943 100644 --- a/test/bdberl_SUITE.erl +++ b/test/bdberl_SUITE.erl @@ -34,7 +34,8 @@ all() -> truncate_all_should_empty_all_databases, btree_stat_should_report_on_success, hash_stat_should_report_on_success, - stat_should_fail_on_bad_dbref]. + stat_should_fail_on_bad_dbref, + lock_stat_should_report_on_success]. dbconfig(Config) -> @@ -277,3 +278,9 @@ hash_stat_should_report_on_success(_Config) -> stat_should_fail_on_bad_dbref(_Config) -> {error, invalid_db} = bdberl:stat(10000000, []), done. + +lock_stat_should_report_on_success(_Config) -> + {ok, Stat} = bdberl:lock_stat([]), + %% Check a lock stat that that probably won't change + 2147483647 = proplists:get_value(cur_maxid, Stat), + done.