From ab2fc3ddc16e47bd0e9b932f57b833f1bf1a6e2e Mon Sep 17 00:00:00 2001 From: William Roe Date: Fri, 12 Apr 2024 12:28:49 +0100 Subject: [PATCH 1/3] Add SQLite3.status to call sqlite3_status This function is useful for understanding the runtime performance of sqlite. This is a very thin layer on top of the sqlite3 library's function, so it allows full usage of it from Ruby. --- ext/sqlite3/sqlite3.c | 17 +++++++++++++++++ lib/sqlite3/constants.rb | 13 +++++++++++++ test/test_sqlite3.rb | 6 ++++++ 3 files changed, 36 insertions(+) diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index c0672a06..122be270 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -85,6 +85,22 @@ threadsafe_p(VALUE UNUSED(klass)) return INT2NUM(sqlite3_threadsafe()); } +static VALUE +status_p(VALUE UNUSED(klass), VALUE opArg, VALUE resetFlagArg) +{ + int op = NUM2INT(opArg); + bool resetFlag = TYPE(resetFlagArg) == T_TRUE; + + int pCurrent = 0; + int pHighwater = 0; + sqlite3_status(op, &pCurrent, &pHighwater, resetFlag); + + VALUE hash = rb_hash_new(); + rb_hash_aset(hash, ID2SYM(rb_intern("current")), INT2FIX(pCurrent)); + rb_hash_aset(hash, ID2SYM(rb_intern("highwater")), INT2FIX(pHighwater)); + return hash; +} + void init_sqlite3_constants(void) { @@ -164,6 +180,7 @@ Init_sqlite3_native(void) rb_define_singleton_method(mSqlite3, "sqlcipher?", using_sqlcipher, 0); rb_define_singleton_method(mSqlite3, "libversion", libversion, 0); rb_define_singleton_method(mSqlite3, "threadsafe", threadsafe_p, 0); + rb_define_singleton_method(mSqlite3, "status", status_p, 2); rb_define_const(mSqlite3, "SQLITE_VERSION", rb_str_new2(SQLITE_VERSION)); rb_define_const(mSqlite3, "SQLITE_VERSION_NUMBER", INT2FIX(SQLITE_VERSION_NUMBER)); rb_define_const(mSqlite3, "SQLITE_LOADED_VERSION", rb_str_new2(sqlite3_libversion())); diff --git a/lib/sqlite3/constants.rb b/lib/sqlite3/constants.rb index 1414a571..beff74db 100644 --- a/lib/sqlite3/constants.rb +++ b/lib/sqlite3/constants.rb @@ -116,5 +116,18 @@ module ErrorCode # sqlite_step() has finished executing DONE = 101 end + + module Status + MEMORY_USED = 0 # This parameter is the current amount of memory checked out using sqlite3_malloc(), either directly or indirectly. The figure includes calls made to sqlite3_malloc() by the application and internal memory usage by the SQLite library. Auxiliary page-cache memory controlled by SQLITE_CONFIG_PAGECACHE is not included in this parameter. The amount returned is the sum of the allocation sizes as reported by the xSize method in sqlite3_mem_methods. + PAGECACHE_USED = 1 # This parameter returns the number of pages used out of the pagecache memory allocator that was configured using SQLITE_CONFIG_PAGECACHE. The value returned is in pages, not in bytes. + PAGECACHE_OVERFLOW = 2 # This parameter returns the number of bytes of page cache allocation which could not be satisfied by the SQLITE_CONFIG_PAGECACHE buffer and where forced to overflow to sqlite3_malloc(). The returned value includes allocations that overflowed because they where too large (they were larger than the "sz" parameter to SQLITE_CONFIG_PAGECACHE) and allocations that overflowed because no space was left in the page cache. + SCRATCH_USED = 3 # NOT USED + SCRATCH_OVERFLOW = 4 # NOT USED + MALLOC_SIZE = 5 # This parameter records the largest memory allocation request handed to sqlite3_malloc() or sqlite3_realloc() (or their internal equivalents). Only the value returned in the *pHighwater parameter to sqlite3_status() is of interest. The value written into the *pCurrent parameter is undefined. + PARSER_STACK = 6 # The *pHighwater parameter records the deepest parser stack. The *pCurrent value is undefined. The *pHighwater value is only meaningful if SQLite is compiled with YYTRACKMAXSTACKDEPTH. + PAGECACHE_SIZE = 7 # This parameter records the largest memory allocation request handed to the pagecache memory allocator. Only the value returned in the *pHighwater parameter to sqlite3_status() is of interest. The value written into the *pCurrent parameter is undefined. + SCRATCH_SIZE = 8 # NOT USED + MALLOC_COUNT = 9 # This parameter records the number of separate memory allocations currently checked out. + end end end diff --git a/test/test_sqlite3.rb b/test/test_sqlite3.rb index 3fe2e2de..59fe91df 100644 --- a/test/test_sqlite3.rb +++ b/test/test_sqlite3.rb @@ -21,5 +21,11 @@ def test_threadsafe? def test_compiled_version_and_loaded_version assert_equal(SQLite3::SQLITE_VERSION, SQLite3::SQLITE_LOADED_VERSION) end + + def test_status + status = SQLite3.status(SQLite3::Constants::Status::MEMORY_USED, false) + assert_not_nil(status.fetch(:current)) + assert_not_nil(status.fetch(:highwater)) + end end end From a1ddbec50f5376f2e3ab67ce192bf8e242b07bdb Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Tue, 16 Apr 2024 10:37:35 -0400 Subject: [PATCH 2/3] doc: document SQLite3::Constants::Status and add to the changelog --- CHANGELOG.md | 1 + lib/sqlite3/constants.rb | 61 +++++++++++++++++++++++++++++++++------- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6912852..863695df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This release drops support for Ruby 2.7. [#453] @flavorjones - Support the `SUPER_JOURNAL` flag which is an alias for `MASTER_JOURNAL` as of sqlite 3.33.0. [#467] @flavorjones - `Statement#stat` and `Statement#memused` introduced to report statistics. [#461] @fractaledmind - `Statement#sql` and `Statement#expanded_sql` introduced to retrieve the SQL statement associated with the `Statement` object. [#293, #498] @tenderlove +- `SQLite3.status` introduced to return run-time status and reset high-water marks. See `SQLite3::Constants::Status` for details. [#520] @wjlroe ### Improved diff --git a/lib/sqlite3/constants.rb b/lib/sqlite3/constants.rb index beff74db..77e82e19 100644 --- a/lib/sqlite3/constants.rb +++ b/lib/sqlite3/constants.rb @@ -117,17 +117,58 @@ module ErrorCode DONE = 101 end + # + # CAPI3REF: Status Parameters + # + # These integer constants designate various run-time status parameters + # that can be returned by SQLite3.status + # module Status - MEMORY_USED = 0 # This parameter is the current amount of memory checked out using sqlite3_malloc(), either directly or indirectly. The figure includes calls made to sqlite3_malloc() by the application and internal memory usage by the SQLite library. Auxiliary page-cache memory controlled by SQLITE_CONFIG_PAGECACHE is not included in this parameter. The amount returned is the sum of the allocation sizes as reported by the xSize method in sqlite3_mem_methods. - PAGECACHE_USED = 1 # This parameter returns the number of pages used out of the pagecache memory allocator that was configured using SQLITE_CONFIG_PAGECACHE. The value returned is in pages, not in bytes. - PAGECACHE_OVERFLOW = 2 # This parameter returns the number of bytes of page cache allocation which could not be satisfied by the SQLITE_CONFIG_PAGECACHE buffer and where forced to overflow to sqlite3_malloc(). The returned value includes allocations that overflowed because they where too large (they were larger than the "sz" parameter to SQLITE_CONFIG_PAGECACHE) and allocations that overflowed because no space was left in the page cache. - SCRATCH_USED = 3 # NOT USED - SCRATCH_OVERFLOW = 4 # NOT USED - MALLOC_SIZE = 5 # This parameter records the largest memory allocation request handed to sqlite3_malloc() or sqlite3_realloc() (or their internal equivalents). Only the value returned in the *pHighwater parameter to sqlite3_status() is of interest. The value written into the *pCurrent parameter is undefined. - PARSER_STACK = 6 # The *pHighwater parameter records the deepest parser stack. The *pCurrent value is undefined. The *pHighwater value is only meaningful if SQLite is compiled with YYTRACKMAXSTACKDEPTH. - PAGECACHE_SIZE = 7 # This parameter records the largest memory allocation request handed to the pagecache memory allocator. Only the value returned in the *pHighwater parameter to sqlite3_status() is of interest. The value written into the *pCurrent parameter is undefined. - SCRATCH_SIZE = 8 # NOT USED - MALLOC_COUNT = 9 # This parameter records the number of separate memory allocations currently checked out. + # This parameter is the current amount of memory checked out using sqlite3_malloc(), either + # directly or indirectly. The figure includes calls made to sqlite3_malloc() by the + # application and internal memory usage by the SQLite library. Auxiliary page-cache memory + # controlled by SQLITE_CONFIG_PAGECACHE is not included in this parameter. The amount returned + # is the sum of the allocation sizes as reported by the xSize method in sqlite3_mem_methods. + MEMORY_USED = 0 + + # This parameter returns the number of pages used out of the pagecache memory allocator that + # was configured using SQLITE_CONFIG_PAGECACHE. The value returned is in pages, not in bytes. + PAGECACHE_USED = 1 + + # This parameter returns the number of bytes of page cache allocation which could not be + # satisfied by the SQLITE_CONFIG_PAGECACHE buffer and where forced to overflow to + # sqlite3_malloc(). The returned value includes allocations that overflowed because they where + # too large (they were larger than the "sz" parameter to SQLITE_CONFIG_PAGECACHE) and + # allocations that overflowed because no space was left in the page cache. + PAGECACHE_OVERFLOW = 2 + + # NOT USED + SCRATCH_USED = 3 + + # NOT USED + SCRATCH_OVERFLOW = 4 + + # This parameter records the largest memory allocation request handed to sqlite3_malloc() or + # sqlite3_realloc() (or their internal equivalents). Only the value returned in the + # *pHighwater parameter to sqlite3_status() is of interest. The value written into the + # *pCurrent parameter is undefined. + MALLOC_SIZE = 5 + + # The *pHighwater parameter records the deepest parser stack. The *pCurrent value is + # undefined. The *pHighwater value is only meaningful if SQLite is compiled with + # YYTRACKMAXSTACKDEPTH. + PARSER_STACK = 6 + + # This parameter records the largest memory allocation request handed to the pagecache memory + # allocator. Only the value returned in the *pHighwater parameter to sqlite3_status() is of + # interest. The value written into the *pCurrent parameter is undefined. + PAGECACHE_SIZE = 7 + + # NOT USED + SCRATCH_SIZE = 8 + + # This parameter records the number of separate memory allocations currently checked out. + MALLOC_COUNT = 9 end end end From b14a84ee1645f2e800fa2c21c22a697c88123a83 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Tue, 16 Apr 2024 10:37:58 -0400 Subject: [PATCH 3/3] make optional the second argument to SQLite3.status and fully document the method. --- ext/sqlite3/sqlite3.c | 26 +++++++++++++++++++++++--- test/test_sqlite3.rb | 14 ++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index 122be270..652c8db5 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -85,11 +85,30 @@ threadsafe_p(VALUE UNUSED(klass)) return INT2NUM(sqlite3_threadsafe()); } +/* + * call-seq: + * status(parameter) → Hash + * status(parameter, reset_flag = false) → Hash + * + * Queries the SQLite3 library for run-time status information. Passing a truthy +reset_flag+ will + * reset the highwater mark to the current value. + * + * [Parameters] + * - +parameter+ (Integer, SQLite3::Constants::Status): The status parameter to query. + * - +reset_flag+ (Boolean): Whether to reset the highwater mark. (default is +false+) + * + * [Returns] + * A Hash containing +:current+ and +:highwater+ keys for integer values. + */ static VALUE -status_p(VALUE UNUSED(klass), VALUE opArg, VALUE resetFlagArg) +rb_sqlite3_status(int argc, VALUE *argv, VALUE klass) { + VALUE opArg, resetFlagArg; + + rb_scan_args(argc, argv, "11", &opArg, &resetFlagArg); + int op = NUM2INT(opArg); - bool resetFlag = TYPE(resetFlagArg) == T_TRUE; + bool resetFlag = RTEST(resetFlagArg); int pCurrent = 0; int pHighwater = 0; @@ -98,6 +117,7 @@ status_p(VALUE UNUSED(klass), VALUE opArg, VALUE resetFlagArg) VALUE hash = rb_hash_new(); rb_hash_aset(hash, ID2SYM(rb_intern("current")), INT2FIX(pCurrent)); rb_hash_aset(hash, ID2SYM(rb_intern("highwater")), INT2FIX(pHighwater)); + return hash; } @@ -180,7 +200,7 @@ Init_sqlite3_native(void) rb_define_singleton_method(mSqlite3, "sqlcipher?", using_sqlcipher, 0); rb_define_singleton_method(mSqlite3, "libversion", libversion, 0); rb_define_singleton_method(mSqlite3, "threadsafe", threadsafe_p, 0); - rb_define_singleton_method(mSqlite3, "status", status_p, 2); + rb_define_singleton_method(mSqlite3, "status", rb_sqlite3_status, -1); rb_define_const(mSqlite3, "SQLITE_VERSION", rb_str_new2(SQLITE_VERSION)); rb_define_const(mSqlite3, "SQLITE_VERSION_NUMBER", INT2FIX(SQLITE_VERSION_NUMBER)); rb_define_const(mSqlite3, "SQLITE_LOADED_VERSION", rb_str_new2(sqlite3_libversion())); diff --git a/test/test_sqlite3.rb b/test/test_sqlite3.rb index 59fe91df..364734b2 100644 --- a/test/test_sqlite3.rb +++ b/test/test_sqlite3.rb @@ -23,9 +23,19 @@ def test_compiled_version_and_loaded_version end def test_status + status = SQLite3.status(SQLite3::Constants::Status::MEMORY_USED) + assert_operator(status.fetch(:current), :>=, 0) + assert_operator(status.fetch(:highwater), :>=, status.fetch(:current)) + end + + def test_status_reset_highwater_mark status = SQLite3.status(SQLite3::Constants::Status::MEMORY_USED, false) - assert_not_nil(status.fetch(:current)) - assert_not_nil(status.fetch(:highwater)) + assert_operator(status.fetch(:current), :>=, 0) + assert_operator(status.fetch(:highwater), :>=, status.fetch(:current)) + + status = SQLite3.status(SQLite3::Constants::Status::MEMORY_USED, true) + assert_operator(status.fetch(:current), :>=, 0) + assert_operator(status.fetch(:highwater), :>=, status.fetch(:current)) end end end