@ -14,6 +14,7 @@
# include "common/hashfn.h"
# include "funcapi.h"
# include "storage/dsm_registry.h"
# include "utils/builtins.h"
# include "utils/pgstat_internal.h"
@ -22,6 +23,8 @@ PG_MODULE_MAGIC_EXT(
. version = PG_VERSION
) ;
# define TEST_CUSTOM_VAR_MAGIC_NUMBER (0xBEEFBEEF)
/*--------------------------------------------------------------------------
* Macros and constants
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -32,6 +35,9 @@ PG_MODULE_MAGIC_EXT(
*/
# define PGSTAT_KIND_TEST_CUSTOM_VAR_STATS 25
/* File paths for auxiliary data serialization */
# define TEST_CUSTOM_AUX_DATA_DESC "pg_stat / test_custom_var_stats_desc.stats"
/*
* Hash statistic name to generate entry index for pgstat lookup .
*/
@ -53,8 +59,23 @@ typedef struct PgStatShared_CustomVarEntry
{
PgStatShared_Common header ; /* standard pgstat entry header */
PgStat_StatCustomVarEntry stats ; /* custom statistics data */
dsa_pointer description ; /* pointer to description string in DSA */
} PgStatShared_CustomVarEntry ;
/*--------------------------------------------------------------------------
* Global Variables
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
/* File handle for auxiliary data serialization */
static FILE * fd_description = NULL ;
/* Current write offset in fd_description file */
static pgoff_t fd_description_offset = 0 ;
/* DSA area for storing variable-length description strings */
static dsa_area * custom_stats_description_dsa = NULL ;
/*--------------------------------------------------------------------------
* Function prototypes
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -64,6 +85,19 @@ typedef struct PgStatShared_CustomVarEntry
static bool test_custom_stats_var_flush_pending_cb ( PgStat_EntryRef * entry_ref ,
bool nowait ) ;
/* Serialization callback: write auxiliary entry data */
static void test_custom_stats_var_to_serialized_data ( const PgStat_HashKey * key ,
const PgStatShared_Common * header ,
FILE * statfile ) ;
/* Deserialization callback: read auxiliary entry data */
static bool test_custom_stats_var_from_serialized_data ( const PgStat_HashKey * key ,
const PgStatShared_Common * header ,
FILE * statfile ) ;
/* Finish callback: end of statistics file operations */
static void test_custom_stats_var_finish ( PgStat_StatsFileOp status ) ;
/*--------------------------------------------------------------------------
* Custom kind configuration
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -80,6 +114,9 @@ static const PgStat_KindInfo custom_stats = {
. shared_data_len = sizeof ( ( ( PgStatShared_CustomVarEntry * ) 0 ) - > stats ) ,
. pending_size = sizeof ( PgStat_StatCustomVarEntry ) ,
. flush_pending_cb = test_custom_stats_var_flush_pending_cb ,
. to_serialized_data = test_custom_stats_var_to_serialized_data ,
. from_serialized_data = test_custom_stats_var_from_serialized_data ,
. finish = test_custom_stats_var_finish ,
} ;
/*--------------------------------------------------------------------------
@ -132,6 +169,310 @@ test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref, bool nowait)
return true ;
}
/*
* test_custom_stats_var_to_serialized_data ( ) -
*
* Serialize auxiliary data ( descriptions ) for custom statistics entries
* to a secondary statistics file . This is called while writing the statistics
* to disk .
*
* This callback writes a mix of data within the main pgstats file and a
* secondary statistics file . The following data is written to the main file for
* each entry :
* - An arbitrary magic number .
* - An offset . This is used to know the location we need to look at
* to retrieve the information from the second file .
*
* The following data is written to the secondary statistics file :
* - The entry key , cross - checked with the data from the main file
* when reloaded .
* - The length of the description .
* - The description data itself .
*/
static void
test_custom_stats_var_to_serialized_data ( const PgStat_HashKey * key ,
const PgStatShared_Common * header ,
FILE * statfile )
{
char * description ;
size_t len ;
PgStatShared_CustomVarEntry * entry = ( PgStatShared_CustomVarEntry * ) header ;
bool found ;
uint32 magic_number = TEST_CUSTOM_VAR_MAGIC_NUMBER ;
/*
* First mark the main file with a magic number , keeping a trace that some
* auxiliary data will exist in the secondary statistics file .
*/
pgstat_write_chunk_s ( statfile , & magic_number ) ;
/* Open statistics file for writing. */
if ( ! fd_description )
{
fd_description = AllocateFile ( TEST_CUSTOM_AUX_DATA_DESC , PG_BINARY_W ) ;
if ( fd_description = = NULL )
{
ereport ( LOG ,
( errcode_for_file_access ( ) ,
errmsg ( " could not open statistics file \" %s \" for writing: %m " ,
TEST_CUSTOM_AUX_DATA_DESC ) ) ) ;
return ;
}
/* Initialize offset for secondary statistics file. */
fd_description_offset = 0 ;
}
/* Write offset to the main data file */
pgstat_write_chunk_s ( statfile , & fd_description_offset ) ;
/*
* First write the entry key to the secondary statistics file . This will
* be cross - checked with the key read from main stats file at loading
* time .
*/
pgstat_write_chunk_s ( fd_description , ( PgStat_HashKey * ) key ) ;
fd_description_offset + = sizeof ( PgStat_HashKey ) ;
if ( ! custom_stats_description_dsa )
custom_stats_description_dsa = GetNamedDSA ( " test_custom_stat_dsa " , & found ) ;
/* Handle entries without descriptions */
if ( ! DsaPointerIsValid ( entry - > description ) | | ! custom_stats_description_dsa )
{
/* length to description file */
len = 0 ;
pgstat_write_chunk_s ( fd_description , & len ) ;
fd_description_offset + = sizeof ( size_t ) ;
return ;
}
/*
* Retrieve description from DSA , then write the length followed by the
* description .
*/
description = dsa_get_address ( custom_stats_description_dsa ,
entry - > description ) ;
len = strlen ( description ) + 1 ;
pgstat_write_chunk_s ( fd_description , & len ) ;
pgstat_write_chunk ( fd_description , description , len ) ;
/*
* Update offset for next entry , counting for the length ( size_t ) of the
* description and the description contents .
*/
fd_description_offset + = len + sizeof ( size_t ) ;
}
/*
* test_custom_stats_var_from_serialized_data ( ) -
*
* Read auxiliary data ( descriptions ) for custom statistics entries from
* the secondary statistics file . This is called while loading the statistics
* at startup .
*
* See the top of test_custom_stats_var_to_serialized_data ( ) for a
* detailed description of the data layout read here .
*/
static bool
test_custom_stats_var_from_serialized_data ( const PgStat_HashKey * key ,
const PgStatShared_Common * header ,
FILE * statfile )
{
PgStatShared_CustomVarEntry * entry ;
dsa_pointer dp ;
size_t len ;
pgoff_t offset ;
char * buffer ;
bool found ;
uint32 magic_number = 0 ;
PgStat_HashKey file_key ;
/* Check the magic number first, in the main file. */
if ( ! pgstat_read_chunk_s ( statfile , & magic_number ) )
{
elog ( WARNING , " failed to read magic number from statistics file " ) ;
return false ;
}
if ( magic_number ! = TEST_CUSTOM_VAR_MAGIC_NUMBER )
{
elog ( WARNING , " found magic number %u from statistics file, should be %u " ,
magic_number , TEST_CUSTOM_VAR_MAGIC_NUMBER ) ;
return false ;
}
/*
* Read the offset from the main stats file , to be able to read the
* auxiliary data from the secondary statistics file .
*/
if ( ! pgstat_read_chunk_s ( statfile , & offset ) )
{
elog ( WARNING , " failed to read metadata offset from statistics file " ) ;
return false ;
}
/* Open statistics file for reading if not already open */
if ( ! fd_description )
{
fd_description = AllocateFile ( TEST_CUSTOM_AUX_DATA_DESC , PG_BINARY_R ) ;
if ( fd_description = = NULL )
{
if ( errno ! = ENOENT )
ereport ( LOG ,
( errcode_for_file_access ( ) ,
errmsg ( " could not open statistics file \" %s \" for reading: %m " ,
TEST_CUSTOM_AUX_DATA_DESC ) ) ) ;
pgstat_reset_of_kind ( PGSTAT_KIND_TEST_CUSTOM_VAR_STATS ) ;
return false ;
}
}
/* Read data from the secondary statistics file, at the specified offset */
if ( fseeko ( fd_description , offset , SEEK_SET ) ! = 0 )
{
elog ( WARNING , " failed to seek to offset %ld in description file " , offset ) ;
return false ;
}
/* Read the hash key from the secondary statistics file */
if ( ! pgstat_read_chunk_s ( fd_description , & file_key ) )
{
elog ( WARNING , " failed to read hash key from file " ) ;
return false ;
}
/* Check key consistency */
if ( file_key . kind ! = key - > kind | |
file_key . dboid ! = key - > dboid | |
file_key . objid ! = key - > objid )
{
elog ( WARNING , " found entry key %u/%u/% " PRIu64 " not matching with %u/%u/% " PRIu64 ,
file_key . kind , file_key . dboid , file_key . objid ,
key - > kind , key - > dboid , key - > objid ) ;
return false ;
}
entry = ( PgStatShared_CustomVarEntry * ) header ;
/* Read the description length and its data */
if ( ! pgstat_read_chunk_s ( fd_description , & len ) )
{
elog ( WARNING , " failed to read metadata length from statistics file " ) ;
return false ;
}
/* Handle empty descriptions */
if ( len = = 0 )
{
entry - > description = InvalidDsaPointer ;
return true ;
}
/* Initialize DSA if needed */
if ( ! custom_stats_description_dsa )
custom_stats_description_dsa = GetNamedDSA ( " test_custom_stat_dsa " , & found ) ;
if ( ! custom_stats_description_dsa )
{
elog ( WARNING , " could not access DSA for custom statistics descriptions " ) ;
return false ;
}
buffer = palloc ( len ) ;
if ( ! pgstat_read_chunk ( fd_description , buffer , len ) )
{
pfree ( buffer ) ;
elog ( WARNING , " failed to read description from file " ) ;
return false ;
}
/* Allocate space in DSA and copy the description */
dp = dsa_allocate ( custom_stats_description_dsa , len ) ;
memcpy ( dsa_get_address ( custom_stats_description_dsa , dp ) , buffer , len ) ;
entry - > description = dp ;
pfree ( buffer ) ;
return true ;
}
/*
* test_custom_stats_var_finish ( ) -
*
* Cleanup function called at the end of statistics file operations .
* Handles closing files and cleanup based on the operation type .
*/
static void
test_custom_stats_var_finish ( PgStat_StatsFileOp status )
{
switch ( status )
{
case STATS_WRITE :
if ( ! fd_description )
return ;
fd_description_offset = 0 ;
/* Check for write errors and cleanup if necessary */
if ( ferror ( fd_description ) )
{
ereport ( LOG ,
( errcode_for_file_access ( ) ,
errmsg ( " could not write to file \" %s \" : %m " ,
TEST_CUSTOM_AUX_DATA_DESC ) ) ) ;
FreeFile ( fd_description ) ;
unlink ( TEST_CUSTOM_AUX_DATA_DESC ) ;
}
else if ( FreeFile ( fd_description ) < 0 )
{
ereport ( LOG ,
( errcode_for_file_access ( ) ,
errmsg ( " could not close file \" %s \" : %m " ,
TEST_CUSTOM_AUX_DATA_DESC ) ) ) ;
unlink ( TEST_CUSTOM_AUX_DATA_DESC ) ;
}
break ;
case STATS_READ :
if ( fd_description )
FreeFile ( fd_description ) ;
/* Remove the file after reading */
elog ( DEBUG2 , " removing file \" %s \" " , TEST_CUSTOM_AUX_DATA_DESC ) ;
unlink ( TEST_CUSTOM_AUX_DATA_DESC ) ;
break ;
case STATS_DISCARD :
{
int ret ;
/* Attempt to remove the file */
ret = unlink ( TEST_CUSTOM_AUX_DATA_DESC ) ;
if ( ret ! = 0 )
{
if ( errno = = ENOENT )
elog ( LOG ,
" didn't need to unlink file \" %s \" - didn't exist " ,
TEST_CUSTOM_AUX_DATA_DESC ) ;
else
ereport ( LOG ,
( errcode_for_file_access ( ) ,
errmsg ( " could not unlink file \" %s \" : %m " ,
TEST_CUSTOM_AUX_DATA_DESC ) ) ) ;
}
else
{
ereport ( LOG ,
( errmsg_internal ( " unlinked file \" %s \" " ,
TEST_CUSTOM_AUX_DATA_DESC ) ) ) ;
}
}
break ;
}
fd_description = NULL ;
}
/*--------------------------------------------------------------------------
* Helper functions
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -162,8 +503,7 @@ test_custom_stats_var_fetch_entry(const char *stat_name)
* test_custom_stats_var_create
* Create new custom statistic entry
*
* Initializes a zero - valued statistics entry in shared memory .
* Validates name length against NAMEDATALEN limit .
* Initializes a statistics entry with the given name and description .
*/
PG_FUNCTION_INFO_V1 ( test_custom_stats_var_create ) ;
Datum
@ -172,6 +512,9 @@ test_custom_stats_var_create(PG_FUNCTION_ARGS)
PgStat_EntryRef * entry_ref ;
PgStatShared_CustomVarEntry * shared_entry ;
char * stat_name = text_to_cstring ( PG_GETARG_TEXT_PP ( 0 ) ) ;
char * description = text_to_cstring ( PG_GETARG_TEXT_PP ( 1 ) ) ;
dsa_pointer dp = InvalidDsaPointer ;
bool found ;
/* Validate name length first */
if ( strlen ( stat_name ) > = NAMEDATALEN )
@ -180,6 +523,20 @@ test_custom_stats_var_create(PG_FUNCTION_ARGS)
errmsg ( " custom statistic name \" %s \" is too long " , stat_name ) ,
errdetail ( " Name must be less than %d characters. " , NAMEDATALEN ) ) ) ;
/* Initialize DSA and description provided */
if ( ! custom_stats_description_dsa )
custom_stats_description_dsa = GetNamedDSA ( " test_custom_stat_dsa " , & found ) ;
if ( ! custom_stats_description_dsa )
ereport ( ERROR ,
( errmsg ( " could not access DSA for custom statistics descriptions " ) ) ) ;
/* Allocate space in DSA and copy description */
dp = dsa_allocate ( custom_stats_description_dsa , strlen ( description ) + 1 ) ;
memcpy ( dsa_get_address ( custom_stats_description_dsa , dp ) ,
description ,
strlen ( description ) + 1 ) ;
/* Create or get existing entry */
entry_ref = pgstat_get_entry_ref_locked ( PGSTAT_KIND_TEST_CUSTOM_VAR_STATS , InvalidOid ,
PGSTAT_CUSTOM_VAR_STATS_IDX ( stat_name ) , true ) ;
@ -192,6 +549,9 @@ test_custom_stats_var_create(PG_FUNCTION_ARGS)
/* Zero-initialize statistics */
memset ( & shared_entry - > stats , 0 , sizeof ( shared_entry - > stats ) ) ;
/* Store description pointer */
shared_entry - > description = dp ;
pgstat_unlock_entry ( entry_ref ) ;
PG_RETURN_VOID ( ) ;
@ -226,8 +586,7 @@ test_custom_stats_var_update(PG_FUNCTION_ARGS)
* test_custom_stats_var_drop
* Remove custom statistic entry
*
* Drops the named statistic from shared memory and requests
* garbage collection if needed .
* Drops the named statistic from shared memory .
*/
PG_FUNCTION_INFO_V1 ( test_custom_stats_var_drop ) ;
Datum
@ -247,7 +606,7 @@ test_custom_stats_var_drop(PG_FUNCTION_ARGS)
* test_custom_stats_var_report
* Retrieve custom statistic values
*
* Returns single row with statistic name and call count if the
* Returns single row with statistic name , call count , and description if the
* statistic exists , otherwise returns no rows .
*/
PG_FUNCTION_INFO_V1 ( test_custom_stats_var_report ) ;
@ -281,9 +640,13 @@ test_custom_stats_var_report(PG_FUNCTION_ARGS)
if ( funcctx - > call_cntr < funcctx - > max_calls )
{
Datum values [ 2 ] ;
bool nulls [ 2 ] = { false , false } ;
Datum values [ 3 ] ;
bool nulls [ 3 ] = { false , false , false } ;
HeapTuple tuple ;
PgStat_EntryRef * entry_ref ;
PgStatShared_CustomVarEntry * shared_entry ;
char * description = NULL ;
bool found ;
stat_name = text_to_cstring ( PG_GETARG_TEXT_PP ( 0 ) ) ;
stat_entry = test_custom_stats_var_fetch_entry ( stat_name ) ;
@ -291,9 +654,33 @@ test_custom_stats_var_report(PG_FUNCTION_ARGS)
/* Return row only if entry exists */
if ( stat_entry )
{
/* Get entry ref to access shared entry */
entry_ref = pgstat_get_entry_ref ( PGSTAT_KIND_TEST_CUSTOM_VAR_STATS , InvalidOid ,
PGSTAT_CUSTOM_VAR_STATS_IDX ( stat_name ) , false , NULL ) ;
if ( entry_ref )
{
shared_entry = ( PgStatShared_CustomVarEntry * ) entry_ref - > shared_stats ;
/* Get description from DSA if available */
if ( DsaPointerIsValid ( shared_entry - > description ) )
{
if ( ! custom_stats_description_dsa )
custom_stats_description_dsa = GetNamedDSA ( " test_custom_stat_dsa " , & found ) ;
if ( custom_stats_description_dsa )
description = dsa_get_address ( custom_stats_description_dsa , shared_entry - > description ) ;
}
}
values [ 0 ] = PointerGetDatum ( cstring_to_text ( stat_name ) ) ;
values [ 1 ] = Int64GetDatum ( stat_entry - > numcalls ) ;
if ( description )
values [ 2 ] = PointerGetDatum ( cstring_to_text ( description ) ) ;
else
nulls [ 2 ] = true ;
tuple = heap_form_tuple ( funcctx - > tuple_desc , values , nulls ) ;
SRF_RETURN_NEXT ( funcctx , HeapTupleGetDatum ( tuple ) ) ;
}