Expose some mdbx statistics/metadata to ruby.

- Add anonymous structs for settings and state to the internal db
    instance struct.
  - Allow tweaking the max_readers setting on db open.
  - Allow altering database sizing/geometry values.
  - Start sketching out some transaction re-entrancy.
  - Refactor initialization slightly, just use the db struct
    instead of additional unnecessary vars.

FossilOrigin-Name: 513f02946f7dc39001c402c1adf0697bb2852ba867685b35adbccaaaf43c6e15
This commit is contained in:
Mahlon E. Smith 2020-12-23 01:10:19 +00:00
parent 770a931d77
commit ceb92fad16
4 changed files with 397 additions and 111 deletions

View file

@ -2,42 +2,18 @@
#include "mdbx_ext.h" #include "mdbx_ext.h"
/* VALUE str = rb_sprintf( "path: %+"PRIsVALUE", opts: %+"PRIsVALUE, path, opts ); */
/* printf( "%s\n", StringValueCStr(str) ); */
VALUE rmdbx_cDatabase;
/* Shortcut for fetching current DB variables. /* Shortcut for fetching current DB variables.
*
*/ */
#define UNWRAP_DB( val, db ) \ #define UNWRAP_DB( val, db ) \
rmdbx_db_t *db; \ rmdbx_db_t *db; \
TypedData_Get_Struct( val, rmdbx_db_t, &rmdbx_db_data, db ); TypedData_Get_Struct( val, rmdbx_db_t, &rmdbx_db_data, db );
/* VALUE rmdbx_cDatabase;
* A struct encapsulating an instance's DB state.
*/
struct rmdbx_db {
MDBX_env *env;
MDBX_dbi dbi;
MDBX_txn *txn;
MDBX_cursor *cursor;
int env_flags;
int mode;
int open;
int max_collections;
char *path;
char *subdb;
};
typedef struct rmdbx_db rmdbx_db_t;
/* /*
* Ruby allocation hook. * Ruby allocation hook.
*/ */
void rmdbx_free( void *db ); /* forward declaration */
static const rb_data_type_t rmdbx_db_data = { static const rb_data_type_t rmdbx_db_data = {
.wrap_struct_name = "MDBX::Database::Data", .wrap_struct_name = "MDBX::Database::Data",
.function = { .dfree = rmdbx_free }, .function = { .dfree = rmdbx_free },
@ -61,13 +37,13 @@ rmdbx_alloc( VALUE klass )
* removed. * removed.
*/ */
void void
rmdbx_close_all( rmdbx_db_t* db ) rmdbx_close_all( rmdbx_db_t *db )
{ {
if ( db->cursor ) mdbx_cursor_close( db->cursor ); if ( db->cursor ) mdbx_cursor_close( db->cursor );
if ( db->txn ) mdbx_txn_abort( db->txn ); if ( db->txn ) mdbx_txn_abort( db->txn );
if ( db->dbi ) mdbx_dbi_close( db->env, db->dbi ); if ( db->dbi ) mdbx_dbi_close( db->env, db->dbi );
if ( db->env ) mdbx_env_close( db->env ); if ( db->env ) mdbx_env_close( db->env );
db->open = 0; db->state.open = 0;
} }
@ -96,6 +72,20 @@ rmdbx_close( VALUE self )
} }
/*
* call-seq:
* db.closed? #=> false
*
* Predicate: return true if the database environment is closed.
*/
VALUE
rmdbx_closed_p( VALUE self )
{
UNWRAP_DB( self, db );
return db->state.open == 1 ? Qfalse : Qtrue;
}
/* /*
* Open the DB environment handle. * Open the DB environment handle.
*/ */
@ -113,48 +103,41 @@ rmdbx_open_env( VALUE self )
rb_raise( rmdbx_eDatabaseError, "mdbx_env_create: (%d) %s", rc, mdbx_strerror(rc) ); rb_raise( rmdbx_eDatabaseError, "mdbx_env_create: (%d) %s", rc, mdbx_strerror(rc) );
/* Set the maximum number of named databases for the environment. */ /* Set the maximum number of named databases for the environment. */
// FIXME: potenially more env setups here? maxreaders, pagesize? mdbx_env_set_maxdbs( db->env, db->settings.max_collections );
mdbx_env_set_maxdbs( db->env, db->max_collections );
rc = mdbx_env_open( db->env, db->path, db->env_flags, db->mode ); /* Customize the maximum number of simultaneous readers. */
if ( db->settings.max_readers )
mdbx_env_set_maxreaders( db->env, db->settings.max_readers );
/* Set an upper boundary (in bytes) for the database map size. */
if ( db->settings.max_size > -1 )
mdbx_env_set_geometry( db->env, -1, -1, db->settings.max_size, -1, -1, -1 );
rc = mdbx_env_open( db->env, db->path, db->settings.env_flags, db->settings.mode );
if ( rc != MDBX_SUCCESS ) { if ( rc != MDBX_SUCCESS ) {
rmdbx_close( self ); rmdbx_close_all( db );
rb_raise( rmdbx_eDatabaseError, "mdbx_env_open: (%d) %s", rc, mdbx_strerror(rc) ); rb_raise( rmdbx_eDatabaseError, "mdbx_env_open: (%d) %s", rc, mdbx_strerror(rc) );
} }
db->open = 1; db->state.open = 1;
return Qtrue; return Qtrue;
} }
/* /*
* call-seq: * Open a new database transaction. If a transaction is already
* db.closed? #=> false * open, this is a no-op.
*
* Predicate: return true if the database environment is closed.
*/
VALUE
rmdbx_closed_p( VALUE self )
{
UNWRAP_DB( self, db );
return db->open == 1 ? Qfalse : Qtrue;
}
/*
* Open a new database transaction.
* *
* +rwflag+ must be either MDBX_TXN_RDONLY or MDBX_TXN_READWRITE. * +rwflag+ must be either MDBX_TXN_RDONLY or MDBX_TXN_READWRITE.
*/ */
void void
rmdbx_open_txn( VALUE self, int rwflag ) rmdbx_open_txn( rmdbx_db_t *db, int rwflag )
{ {
int rc; if ( db->txn ) return;
UNWRAP_DB( self, db );
rc = mdbx_txn_begin( db->env, NULL, rwflag, &db->txn); int rc = mdbx_txn_begin( db->env, NULL, rwflag, &db->txn);
if ( rc != MDBX_SUCCESS ) { if ( rc != MDBX_SUCCESS ) {
rmdbx_close( self ); rmdbx_close_all( db );
rb_raise( rmdbx_eDatabaseError, "mdbx_txn_begin: (%d) %s", rc, mdbx_strerror(rc) ); rb_raise( rmdbx_eDatabaseError, "mdbx_txn_begin: (%d) %s", rc, mdbx_strerror(rc) );
} }
@ -162,7 +145,7 @@ rmdbx_open_txn( VALUE self, int rwflag )
// FIXME: dbi_flags // FIXME: dbi_flags
rc = mdbx_dbi_open( db->txn, db->subdb, MDBX_CREATE, &db->dbi ); rc = mdbx_dbi_open( db->txn, db->subdb, MDBX_CREATE, &db->dbi );
if ( rc != MDBX_SUCCESS ) { if ( rc != MDBX_SUCCESS ) {
rmdbx_close( self ); rmdbx_close_all( db );
rb_raise( rmdbx_eDatabaseError, "mdbx_dbi_open: (%d) %s", rc, mdbx_strerror(rc) ); rb_raise( rmdbx_eDatabaseError, "mdbx_dbi_open: (%d) %s", rc, mdbx_strerror(rc) );
} }
} }
@ -171,6 +154,32 @@ rmdbx_open_txn( VALUE self, int rwflag )
} }
/*
* Close any existing database transaction. If there is no
* active transaction, this is a no-op.
*
* FIXME: this needs a conditional no-op for long running
* transactions, so callers don't have to care/check
*
* +txnflag must either be RMDBX_TXN_ROLLBACK or RMDBX_TXN_COMMIT.
*/
void
rmdbx_close_txn( rmdbx_db_t *db, int txnflag )
{
if ( ! db->txn ) return;
switch ( txnflag ) {
case RMDBX_TXN_COMMIT:
mdbx_txn_commit( db->txn );
default:
mdbx_txn_abort( db->txn );
}
db->txn = 0;
return;
}
/* /*
* call-seq: * call-seq:
* db.clear * db.clear
@ -182,13 +191,13 @@ rmdbx_clear( VALUE self )
{ {
UNWRAP_DB( self, db ); UNWRAP_DB( self, db );
rmdbx_open_txn( self, MDBX_TXN_READWRITE ); rmdbx_open_txn( db, MDBX_TXN_READWRITE );
int rc = mdbx_drop( db->txn, db->dbi, true ); int rc = mdbx_drop( db->txn, db->dbi, true );
if ( rc != MDBX_SUCCESS ) if ( rc != MDBX_SUCCESS )
rb_raise( rmdbx_eDatabaseError, "mdbx_drop: (%d) %s", rc, mdbx_strerror(rc) ); rb_raise( rmdbx_eDatabaseError, "mdbx_drop: (%d) %s", rc, mdbx_strerror(rc) );
mdbx_txn_commit( db->txn ); rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
/* Refresh the environment handles. */ /* Refresh the environment handles. */
rmdbx_open_env( self ); rmdbx_open_env( self );
@ -249,9 +258,9 @@ rmdbx_keys( VALUE self )
MDBX_val key, data; MDBX_val key, data;
int rc; int rc;
if ( ! db->open ) rb_raise( rmdbx_eDatabaseError, "Closed database." ); if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
rmdbx_open_txn( self, MDBX_TXN_RDONLY ); rmdbx_open_txn( db, MDBX_TXN_RDONLY );
rc = mdbx_cursor_open( db->txn, db->dbi, &db->cursor); rc = mdbx_cursor_open( db->txn, db->dbi, &db->cursor);
if ( rc != MDBX_SUCCESS ) { if ( rc != MDBX_SUCCESS ) {
@ -269,7 +278,7 @@ rmdbx_keys( VALUE self )
mdbx_cursor_close( db->cursor ); mdbx_cursor_close( db->cursor );
db->cursor = NULL; db->cursor = NULL;
mdbx_txn_abort( db->txn ); rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
return rv; return rv;
} }
@ -286,14 +295,14 @@ rmdbx_get_val( VALUE self, VALUE key )
VALUE deserialize_proc; VALUE deserialize_proc;
UNWRAP_DB( self, db ); UNWRAP_DB( self, db );
if ( ! db->open ) rb_raise( rmdbx_eDatabaseError, "Closed database." ); if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
rmdbx_open_txn( self, MDBX_TXN_RDONLY ); rmdbx_open_txn( db, MDBX_TXN_RDONLY );
MDBX_val ckey = rmdbx_key_for( key ); MDBX_val ckey = rmdbx_key_for( key );
MDBX_val data; MDBX_val data;
rc = mdbx_get( db->txn, db->dbi, &ckey, &data ); rc = mdbx_get( db->txn, db->dbi, &ckey, &data );
mdbx_txn_abort( db->txn ); rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
switch ( rc ) { switch ( rc ) {
case MDBX_SUCCESS: case MDBX_SUCCESS:
@ -324,9 +333,9 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val )
int rc; int rc;
UNWRAP_DB( self, db ); UNWRAP_DB( self, db );
if ( ! db->open ) rb_raise( rmdbx_eDatabaseError, "Closed database." ); if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
rmdbx_open_txn( self, MDBX_TXN_READWRITE ); rmdbx_open_txn( db, MDBX_TXN_READWRITE );
MDBX_val ckey = rmdbx_key_for( key ); MDBX_val ckey = rmdbx_key_for( key );
@ -341,7 +350,7 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val )
rc = mdbx_replace( db->txn, db->dbi, &ckey, &data, &old, 0 ); rc = mdbx_replace( db->txn, db->dbi, &ckey, &data, &old, 0 );
} }
mdbx_txn_commit( db->txn ); rmdbx_close_txn( db, RMDBX_TXN_COMMIT );
switch ( rc ) { switch ( rc ) {
case MDBX_SUCCESS: case MDBX_SUCCESS:
@ -354,6 +363,24 @@ rmdbx_put_val( VALUE self, VALUE key, VALUE val )
} }
/*
* call-seq:
* db.statistics #=> (hash of stats)
*
* Returns a hash populated with various metadata for the opened
* database.
*
*/
VALUE
rmdbx_stats( VALUE self )
{
UNWRAP_DB( self, db );
if ( ! db->state.open ) rb_raise( rmdbx_eDatabaseError, "Closed database." );
return rmdbx_gather_stats( db );
}
/* /*
* call-seq: * call-seq:
* db.collection( 'collection_name' ) # => db * db.collection( 'collection_name' ) # => db
@ -409,11 +436,7 @@ rmdbx_set_subdb( int argc, VALUE *argv, VALUE self )
VALUE VALUE
rmdbx_database_initialize( int argc, VALUE *argv, VALUE self ) rmdbx_database_initialize( int argc, VALUE *argv, VALUE self )
{ {
int mode = 0644;
int max_collections = 0;
int env_flags = MDBX_ENV_DEFAULTS;
VALUE path, opts, opt; VALUE path, opts, opt;
rb_scan_args( argc, argv, "11", &path, &opts ); rb_scan_args( argc, argv, "11", &path, &opts );
/* Ensure options is a hash if it was passed in. /* Ensure options is a hash if it was passed in.
@ -426,51 +449,57 @@ rmdbx_database_initialize( int argc, VALUE *argv, VALUE self )
} }
rb_hash_freeze( opts ); rb_hash_freeze( opts );
/* Options setup, overrides.
*/
opt = rb_hash_aref( opts, ID2SYM( rb_intern("mode") ) );
if ( ! NIL_P(opt) ) mode = FIX2INT( opt );
opt = rb_hash_aref( opts, ID2SYM( rb_intern("max_collections") ) );
if ( ! NIL_P(opt) ) max_collections = FIX2INT( opt );
opt = rb_hash_aref( opts, ID2SYM( rb_intern("nosubdir") ) );
if ( RTEST(opt) ) env_flags = env_flags | MDBX_NOSUBDIR;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("readonly") ) );
if ( RTEST(opt) ) env_flags = env_flags | MDBX_RDONLY;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("exclusive") ) );
if ( RTEST(opt) ) env_flags = env_flags | MDBX_EXCLUSIVE;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("compat") ) );
if ( RTEST(opt) ) env_flags = env_flags | MDBX_ACCEDE;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("writemap") ) );
if ( RTEST(opt) ) env_flags = env_flags | MDBX_WRITEMAP;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_threadlocal") ) );
if ( RTEST(opt) ) env_flags = env_flags | MDBX_NOTLS;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_readahead") ) );
if ( RTEST(opt) ) env_flags = env_flags | MDBX_NORDAHEAD;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_memory_init") ) );
if ( RTEST(opt) ) env_flags = env_flags | MDBX_NOMEMINIT;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("coalesce") ) );
if ( RTEST(opt) ) env_flags = env_flags | MDBX_COALESCE;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("lifo_reclaim") ) );
if ( RTEST(opt) ) env_flags = env_flags | MDBX_LIFORECLAIM;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_metasync") ) );
if ( RTEST(opt) ) env_flags = env_flags | MDBX_NOMETASYNC;
/* Duplicate keys, on mdbx_dbi_open, maybe set here? */
/* MDBX_DUPSORT = UINT32_C(0x04), */
/* Initialize the DB vals. /* Initialize the DB vals.
*/ */
UNWRAP_DB( self, db ); UNWRAP_DB( self, db );
db->env = NULL; db->env = NULL;
db->dbi = 0; db->dbi = 0;
db->txn = NULL; db->txn = NULL;
db->cursor = NULL; db->cursor = NULL;
db->env_flags = env_flags; db->path = StringValueCStr( path );
db->mode = mode; db->subdb = NULL;
db->max_collections = max_collections; db->state.open = 0;
db->path = StringValueCStr( path ); db->settings.env_flags = MDBX_ENV_DEFAULTS;
db->open = 0; db->settings.mode = 0644;
db->subdb = NULL; db->settings.max_collections = 0;
db->settings.max_readers = 0;
db->settings.max_size = -1;
/* Options setup, overrides.
*/
opt = rb_hash_aref( opts, ID2SYM( rb_intern("mode") ) );
if ( ! NIL_P(opt) ) db->settings.mode = FIX2INT( opt );
opt = rb_hash_aref( opts, ID2SYM( rb_intern("max_collections") ) );
if ( ! NIL_P(opt) ) db->settings.max_collections = FIX2INT( opt );
opt = rb_hash_aref( opts, ID2SYM( rb_intern("max_readers") ) );
if ( ! NIL_P(opt) ) db->settings.max_readers = FIX2INT( opt );
opt = rb_hash_aref( opts, ID2SYM( rb_intern("max_size") ) );
if ( ! NIL_P(opt) ) db->settings.max_size = NUM2LONG( opt );
opt = rb_hash_aref( opts, ID2SYM( rb_intern("nosubdir") ) );
if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOSUBDIR;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("readonly") ) );
if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_RDONLY;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("exclusive") ) );
if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_EXCLUSIVE;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("compat") ) );
if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_ACCEDE;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("writemap") ) );
if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_WRITEMAP;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_threadlocal") ) );
if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOTLS;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_readahead") ) );
if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NORDAHEAD;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_memory_init") ) );
if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOMEMINIT;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("coalesce") ) );
if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_COALESCE;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("lifo_reclaim") ) );
if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_LIFORECLAIM;
opt = rb_hash_aref( opts, ID2SYM( rb_intern("no_metasync") ) );
if ( RTEST(opt) ) db->settings.env_flags = db->settings.env_flags | MDBX_NOMETASYNC;
/* Duplicate keys, on mdbx_dbi_open, maybe set here? */
/* MDBX_DUPSORT = UINT32_C(0x04), */
/* Set instance variables. /* Set instance variables.
*/ */
@ -506,6 +535,8 @@ rmdbx_init_database()
rb_define_method( rmdbx_cDatabase, "[]", rmdbx_get_val, 1 ); rb_define_method( rmdbx_cDatabase, "[]", rmdbx_get_val, 1 );
rb_define_method( rmdbx_cDatabase, "[]=", rmdbx_put_val, 2 ); rb_define_method( rmdbx_cDatabase, "[]=", rmdbx_put_val, 2 );
rb_define_protected_method( rmdbx_cDatabase, "raw_stats", rmdbx_stats, 0 );
rb_require( "mdbx/database" ); rb_require( "mdbx/database" );
} }

View file

@ -7,6 +7,39 @@
#ifndef MDBX_EXT_0_9_2 #ifndef MDBX_EXT_0_9_2
#define MDBX_EXT_0_9_2 #define MDBX_EXT_0_9_2
#define RMDBX_TXN_ROLLBACK 0
#define RMDBX_TXN_COMMIT 1
/*
* A struct encapsulating an instance's DB
* state and settings.
*/
struct rmdbx_db {
MDBX_env *env;
MDBX_dbi dbi;
MDBX_txn *txn;
MDBX_cursor *cursor;
struct {
int env_flags;
int mode;
int open;
int max_collections;
int max_readers;
uint64_t max_size;
} settings;
struct {
int open;
} state;
char *path;
char *subdb;
};
typedef struct rmdbx_db rmdbx_db_t;
static const rb_data_type_t rmdbx_db_data;
extern void rmdbx_free( void *db ); /* forward declaration for the allocator */
/* ------------------------------------------------------------ /* ------------------------------------------------------------
* Globals * Globals
@ -23,6 +56,10 @@ extern VALUE rmdbx_eRollback;
* ------------------------------------------------------------ */ * ------------------------------------------------------------ */
extern void Init_rmdbx ( void ); extern void Init_rmdbx ( void );
extern void rmdbx_init_database ( void ); extern void rmdbx_init_database ( void );
extern void rmdbx_open_txn( rmdbx_db_t*, int );
extern void rmdbx_close_txn( rmdbx_db_t*, int );
extern VALUE rmdbx_gather_stats( rmdbx_db_t* );
#endif /* define MDBX_EXT_0_9_2 */ #endif /* define MDBX_EXT_0_9_2 */

192
ext/mdbx_ext/stats.c Normal file
View file

@ -0,0 +1,192 @@
/* vim: set noet sta sw=4 ts=4 :
*
* Expose a bunch of mdbx internals to ruby.
* This is all largely stolen from mdbx_stat.c.
*
* Entry point is rmdbx_stats() in database.c.
*
*/
#include "mdbx_ext.h"
/*
* Metadata specific to the mdbx build.
*/
void
rmdbx_gather_build_stats( VALUE stat )
{
rb_hash_aset( stat, ID2SYM(rb_intern("build_compiler")),
rb_str_new_cstr(mdbx_build.compiler) );
rb_hash_aset( stat, ID2SYM(rb_intern("build_flags")),
rb_str_new_cstr(mdbx_build.flags) );
rb_hash_aset( stat, ID2SYM(rb_intern("build_options")),
rb_str_new_cstr(mdbx_build.options) );
rb_hash_aset( stat, ID2SYM(rb_intern("build_target")),
rb_str_new_cstr(mdbx_build.target) );
return;
}
/*
* Metadata for the database file.
*/
void
rmdbx_gather_datafile_stats(
VALUE environ,
MDBX_stat mstat,
MDBX_envinfo menvinfo )
{
VALUE datafile = rb_hash_new();
rb_hash_aset( environ, ID2SYM(rb_intern("datafile")), datafile );
rb_hash_aset( datafile, ID2SYM(rb_intern("size_current")),
INT2NUM(menvinfo.mi_geo.current) );
rb_hash_aset( datafile, ID2SYM(rb_intern("pages")),
INT2NUM(menvinfo.mi_geo.current / mstat.ms_psize) );
if ( menvinfo.mi_geo.lower != menvinfo.mi_geo.upper ) {
rb_hash_aset( datafile, ID2SYM(rb_intern("type")),
rb_str_new_cstr("dynamic") );
rb_hash_aset( datafile, ID2SYM(rb_intern("size_lower")),
INT2NUM( menvinfo.mi_geo.lower ) );
rb_hash_aset( datafile, ID2SYM(rb_intern("size_upper")),
LONG2FIX( menvinfo.mi_geo.upper ) );
rb_hash_aset( datafile, ID2SYM(rb_intern("growth_step")),
INT2NUM( menvinfo.mi_geo.grow ) );
rb_hash_aset( datafile, ID2SYM(rb_intern("shrink_threshold")),
INT2NUM( menvinfo.mi_geo.shrink ) );
}
else {
rb_hash_aset( datafile, ID2SYM(rb_intern("type")),
rb_str_new_cstr("fixed") );
}
return;
}
/*
* Metadata for the database environment.
*/
void
rmdbx_gather_environment_stats(
VALUE stat,
MDBX_stat mstat,
MDBX_envinfo menvinfo )
{
VALUE environ = rb_hash_new();
rb_hash_aset( stat, ID2SYM(rb_intern("environment")), environ );
rb_hash_aset( environ, ID2SYM(rb_intern("pagesize")),
INT2NUM(mstat.ms_psize) );
rb_hash_aset( environ, ID2SYM(rb_intern("last_txnid")),
INT2NUM(menvinfo.mi_recent_txnid) );
rb_hash_aset( environ, ID2SYM(rb_intern("last_reader_txnid")),
INT2NUM(menvinfo.mi_latter_reader_txnid) );
rb_hash_aset( environ, ID2SYM(rb_intern("maximum_readers")),
INT2NUM(menvinfo.mi_maxreaders) );
rb_hash_aset( environ, ID2SYM(rb_intern("readers_in_use")),
INT2NUM(menvinfo.mi_numreaders) );
rmdbx_gather_datafile_stats( environ, mstat, menvinfo );
return;
}
/*
* Callback iterator for pulling each reader's current state.
* See: https://erthink.github.io/libmdbx/group__c__statinfo.html#gad1ab5cf54d4a9f7d4c2999078920e8b0
*
*/
int
reader_list_callback(
void *ctx,
int num,
int slot,
mdbx_pid_t pid,
mdbx_tid_t thread,
uint64_t txnid,
uint64_t lag,
size_t bytes_used,
size_t bytes_retired )
{
VALUE reader = rb_hash_new();
rb_hash_aset( reader, ID2SYM(rb_intern("slot")),
INT2NUM( slot ) );
rb_hash_aset( reader, ID2SYM(rb_intern("pid")),
LONG2FIX( pid ) );
rb_hash_aset( reader, ID2SYM(rb_intern("thread")),
LONG2FIX( thread ) );
rb_hash_aset( reader, ID2SYM(rb_intern("txnid")),
LONG2FIX( txnid ) );
rb_hash_aset( reader, ID2SYM(rb_intern("lag")),
LONG2FIX( lag ) );
rb_hash_aset( reader, ID2SYM(rb_intern("bytes_used")),
LONG2FIX( bytes_used ) );
rb_hash_aset( reader, ID2SYM(rb_intern("bytes_retired")),
LONG2FIX( bytes_retired ) );
rb_ary_push( (VALUE)ctx, reader );
return 0;
}
/*
* Metadata for current reader slots.
* Initialize an array and populate it with each reader's statistics.
*/
void
rmdbx_gather_reader_stats(
rmdbx_db_t *db,
VALUE stat,
MDBX_stat mstat,
MDBX_envinfo menvinfo )
{
VALUE readers = rb_ary_new();
mdbx_reader_list( db->env, reader_list_callback, (void*)readers );
rb_hash_aset( stat, ID2SYM(rb_intern("readers")), readers );
return;
}
/*
* Build and return a hash of various statistic/metadata
* for the open +db+ handle.
*/
VALUE
rmdbx_gather_stats( rmdbx_db_t *db )
{
VALUE stat = rb_hash_new();
int rc;
MDBX_stat mstat;
MDBX_envinfo menvinfo;
rmdbx_gather_build_stats( stat );
rmdbx_open_txn( db, MDBX_TXN_RDONLY );
rc = mdbx_env_info_ex( db->env, db->txn, &menvinfo, sizeof(menvinfo) );
if ( rc != MDBX_SUCCESS )
rb_raise( rmdbx_eDatabaseError, "mdbx_env_info_ex: (%d) %s", rc, mdbx_strerror(rc) );
rc = mdbx_env_stat_ex( db->env, db->txn, &mstat, sizeof(mstat) );
if ( rc != MDBX_SUCCESS )
rb_raise( rmdbx_eDatabaseError, "mdbx_env_stat_ex: (%d) %s", rc, mdbx_strerror(rc) );
rmdbx_close_txn( db, RMDBX_TXN_ROLLBACK );
rmdbx_gather_environment_stats( stat, mstat, menvinfo );
rmdbx_gather_reader_stats( db, stat, mstat, menvinfo );
/* database and subdatabases */
return stat;
}

View file

@ -18,7 +18,7 @@ class MDBX::Database
### db[ 'key' ] #=> value ### db[ 'key' ] #=> value
### end ### end
### ###
### FIXME: options! ### FIXME: document all options!
### ###
def self::open( *args, &block ) def self::open( *args, &block )
db = new( *args ) db = new( *args )
@ -64,5 +64,31 @@ class MDBX::Database
# Allow for some common nomenclature. # Allow for some common nomenclature.
alias_method :namespace, :collection alias_method :namespace, :collection
### Return a hash of various metadata for the current database.
###
def statistics
raw = self.raw_stats
# Place build options in their own hash.
#
build_opts = raw.delete( :build_options ).split.each_with_object( {} ) do |opt, acc|
key, val = opt.split( '=' )
acc[ key.to_sym ] = Integer( val ) rescue val
end
stats = {
build: {
compiler: raw.delete( :build_compiler ),
flags: raw.delete( :build_flags ),
options: build_opts,
target: raw.delete( :build_target )
}
}
stats.merge!( raw )
return stats
end
end # class MDBX::Database end # class MDBX::Database