LCOV - code coverage report
Current view: top level - src/test/modules/test_custom_stats - test_custom_var_stats.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 147 194 75.8 %
Date: 2025-12-17 06:18:05 Functions: 15 15 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*------------------------------------------------------------------------------------
       2             :  *
       3             :  * test_custom_var_stats.c
       4             :  *      Test module for variable-sized custom pgstats
       5             :  *
       6             :  * Copyright (c) 2025, PostgreSQL Global Development Group
       7             :  *
       8             :  * IDENTIFICATION
       9             :  *      src/test/modules/test_custom_var_stats/test_custom_var_stats.c
      10             :  *
      11             :  * ------------------------------------------------------------------------------------
      12             :  */
      13             : #include "postgres.h"
      14             : 
      15             : #include "common/hashfn.h"
      16             : #include "funcapi.h"
      17             : #include "storage/dsm_registry.h"
      18             : #include "utils/builtins.h"
      19             : #include "utils/pgstat_internal.h"
      20             : 
      21           6 : PG_MODULE_MAGIC_EXT(
      22             :                     .name = "test_custom_var_stats",
      23             :                     .version = PG_VERSION
      24             : );
      25             : 
      26             : #define TEST_CUSTOM_VAR_MAGIC_NUMBER (0xBEEFBEEF)
      27             : 
      28             : /*--------------------------------------------------------------------------
      29             :  * Macros and constants
      30             :  *--------------------------------------------------------------------------
      31             :  */
      32             : 
      33             : /*
      34             :  * Kind ID for test_custom_var_stats statistics.
      35             :  */
      36             : #define PGSTAT_KIND_TEST_CUSTOM_VAR_STATS 25
      37             : 
      38             : /* File paths for auxiliary data serialization */
      39             : #define TEST_CUSTOM_AUX_DATA_DESC "pg_stat/test_custom_var_stats_desc.stats"
      40             : 
      41             : /*
      42             :  * Hash statistic name to generate entry index for pgstat lookup.
      43             :  */
      44             : #define PGSTAT_CUSTOM_VAR_STATS_IDX(name) hash_bytes_extended((const unsigned char *) name, strlen(name), 0)
      45             : 
      46             : /*--------------------------------------------------------------------------
      47             :  * Type definitions
      48             :  *--------------------------------------------------------------------------
      49             :  */
      50             : 
      51             : /* Backend-local pending statistics before flush to shared memory */
      52             : typedef struct PgStat_StatCustomVarEntry
      53             : {
      54             :     PgStat_Counter numcalls;    /* times statistic was incremented */
      55             : } PgStat_StatCustomVarEntry;
      56             : 
      57             : /* Shared memory statistics entry visible to all backends */
      58             : typedef struct PgStatShared_CustomVarEntry
      59             : {
      60             :     PgStatShared_Common header; /* standard pgstat entry header */
      61             :     PgStat_StatCustomVarEntry stats;    /* custom statistics data */
      62             :     dsa_pointer description;    /* pointer to description string in DSA */
      63             : } PgStatShared_CustomVarEntry;
      64             : 
      65             : /*--------------------------------------------------------------------------
      66             :  * Global Variables
      67             :  *--------------------------------------------------------------------------
      68             :  */
      69             : 
      70             : /* File handle for auxiliary data serialization */
      71             : static FILE *fd_description = NULL;
      72             : 
      73             : /* Current write offset in fd_description file */
      74             : static pgoff_t fd_description_offset = 0;
      75             : 
      76             : /* DSA area for storing variable-length description strings */
      77             : static dsa_area *custom_stats_description_dsa = NULL;
      78             : 
      79             : /*--------------------------------------------------------------------------
      80             :  * Function prototypes
      81             :  *--------------------------------------------------------------------------
      82             :  */
      83             : 
      84             : /* Flush callback: merge pending stats into shared memory */
      85             : static bool test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref,
      86             :                                                    bool nowait);
      87             : 
      88             : /* Serialization callback: write auxiliary entry data */
      89             : static void test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key,
      90             :                                                      const PgStatShared_Common *header,
      91             :                                                      FILE *statfile);
      92             : 
      93             : /* Deserialization callback: read auxiliary entry data */
      94             : static bool test_custom_stats_var_from_serialized_data(const PgStat_HashKey *key,
      95             :                                                        const PgStatShared_Common *header,
      96             :                                                        FILE *statfile);
      97             : 
      98             : /* Finish callback: end of statistics file operations */
      99             : static void test_custom_stats_var_finish(PgStat_StatsFileOp status);
     100             : 
     101             : /*--------------------------------------------------------------------------
     102             :  * Custom kind configuration
     103             :  *--------------------------------------------------------------------------
     104             :  */
     105             : 
     106             : static const PgStat_KindInfo custom_stats = {
     107             :     .name = "test_custom_var_stats",
     108             :     .fixed_amount = false,      /* variable number of entries */
     109             :     .write_to_file = true,      /* persist across restarts */
     110             :     .track_entry_count = true,  /* count active entries */
     111             :     .accessed_across_databases = true,  /* global statistics */
     112             :     .shared_size = sizeof(PgStatShared_CustomVarEntry),
     113             :     .shared_data_off = offsetof(PgStatShared_CustomVarEntry, stats),
     114             :     .shared_data_len = sizeof(((PgStatShared_CustomVarEntry *) 0)->stats),
     115             :     .pending_size = sizeof(PgStat_StatCustomVarEntry),
     116             :     .flush_pending_cb = test_custom_stats_var_flush_pending_cb,
     117             :     .to_serialized_data = test_custom_stats_var_to_serialized_data,
     118             :     .from_serialized_data = test_custom_stats_var_from_serialized_data,
     119             :     .finish = test_custom_stats_var_finish,
     120             : };
     121             : 
     122             : /*--------------------------------------------------------------------------
     123             :  * Module initialization
     124             :  *--------------------------------------------------------------------------
     125             :  */
     126             : 
     127             : void
     128           6 : _PG_init(void)
     129             : {
     130             :     /* Must be loaded via shared_preload_libraries */
     131           6 :     if (!process_shared_preload_libraries_in_progress)
     132           0 :         return;
     133             : 
     134             :     /* Register custom statistics kind */
     135           6 :     pgstat_register_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, &custom_stats);
     136             : }
     137             : 
     138             : /*--------------------------------------------------------------------------
     139             :  * Statistics callback functions
     140             :  *--------------------------------------------------------------------------
     141             :  */
     142             : 
     143             : /*
     144             :  * test_custom_stats_var_flush_pending_cb
     145             :  *      Merge pending backend statistics into shared memory
     146             :  *
     147             :  * Called by pgstat collector to flush accumulated local statistics
     148             :  * to shared memory where other backends can read them.
     149             :  *
     150             :  * Returns false only if nowait=true and lock acquisition fails.
     151             :  */
     152             : static bool
     153          20 : test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref, bool nowait)
     154             : {
     155             :     PgStat_StatCustomVarEntry *pending_entry;
     156             :     PgStatShared_CustomVarEntry *shared_entry;
     157             : 
     158          20 :     pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending;
     159          20 :     shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
     160             : 
     161          20 :     if (!pgstat_lock_entry(entry_ref, nowait))
     162           0 :         return false;
     163             : 
     164             :     /* Add pending counts to shared totals */
     165          20 :     shared_entry->stats.numcalls += pending_entry->numcalls;
     166             : 
     167          20 :     pgstat_unlock_entry(entry_ref);
     168             : 
     169          20 :     return true;
     170             : }
     171             : 
     172             : /*
     173             :  * test_custom_stats_var_to_serialized_data() -
     174             :  *
     175             :  * Serialize auxiliary data (descriptions) for custom statistics entries
     176             :  * to a secondary statistics file. This is called while writing the statistics
     177             :  * to disk.
     178             :  *
     179             :  * This callback writes a mix of data within the main pgstats file and a
     180             :  * secondary statistics file.  The following data is written to the main file for
     181             :  * each entry:
     182             :  * - An arbitrary magic number.
     183             :  * - An offset.  This is used to know the location we need to look at
     184             :  * to retrieve the information from the second file.
     185             :  *
     186             :  * The following data is written to the secondary statistics file:
     187             :  * - The entry key, cross-checked with the data from the main file
     188             :  * when reloaded.
     189             :  * - The length of the description.
     190             :  * - The description data itself.
     191             :  */
     192             : static void
     193           4 : test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key,
     194             :                                          const PgStatShared_Common *header,
     195             :                                          FILE *statfile)
     196             : {
     197             :     char       *description;
     198             :     size_t      len;
     199           4 :     PgStatShared_CustomVarEntry *entry = (PgStatShared_CustomVarEntry *) header;
     200             :     bool        found;
     201           4 :     uint32      magic_number = TEST_CUSTOM_VAR_MAGIC_NUMBER;
     202             : 
     203             :     /*
     204             :      * First mark the main file with a magic number, keeping a trace that some
     205             :      * auxiliary data will exist in the secondary statistics file.
     206             :      */
     207           4 :     pgstat_write_chunk_s(statfile, &magic_number);
     208             : 
     209             :     /* Open statistics file for writing. */
     210           4 :     if (!fd_description)
     211             :     {
     212           2 :         fd_description = AllocateFile(TEST_CUSTOM_AUX_DATA_DESC, PG_BINARY_W);
     213           2 :         if (fd_description == NULL)
     214             :         {
     215           0 :             ereport(LOG,
     216             :                     (errcode_for_file_access(),
     217             :                      errmsg("could not open statistics file \"%s\" for writing: %m",
     218             :                             TEST_CUSTOM_AUX_DATA_DESC)));
     219           0 :             return;
     220             :         }
     221             : 
     222             :         /* Initialize offset for secondary statistics file. */
     223           2 :         fd_description_offset = 0;
     224             :     }
     225             : 
     226             :     /* Write offset to the main data file */
     227           4 :     pgstat_write_chunk_s(statfile, &fd_description_offset);
     228             : 
     229             :     /*
     230             :      * First write the entry key to the secondary statistics file.  This will
     231             :      * be cross-checked with the key read from main stats file at loading
     232             :      * time.
     233             :      */
     234           4 :     pgstat_write_chunk_s(fd_description, (PgStat_HashKey *) key);
     235           4 :     fd_description_offset += sizeof(PgStat_HashKey);
     236             : 
     237           4 :     if (!custom_stats_description_dsa)
     238           2 :         custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
     239             : 
     240             :     /* Handle entries without descriptions */
     241           4 :     if (!DsaPointerIsValid(entry->description) || !custom_stats_description_dsa)
     242             :     {
     243             :         /* length to description file */
     244           0 :         len = 0;
     245           0 :         pgstat_write_chunk_s(fd_description, &len);
     246           0 :         fd_description_offset += sizeof(size_t);
     247           0 :         return;
     248             :     }
     249             : 
     250             :     /*
     251             :      * Retrieve description from DSA, then write the length followed by the
     252             :      * description.
     253             :      */
     254           4 :     description = dsa_get_address(custom_stats_description_dsa,
     255             :                                   entry->description);
     256           4 :     len = strlen(description) + 1;
     257           4 :     pgstat_write_chunk_s(fd_description, &len);
     258           4 :     pgstat_write_chunk(fd_description, description, len);
     259             : 
     260             :     /*
     261             :      * Update offset for next entry, counting for the length (size_t) of the
     262             :      * description and the description contents.
     263             :      */
     264           4 :     fd_description_offset += len + sizeof(size_t);
     265             : }
     266             : 
     267             : /*
     268             :  * test_custom_stats_var_from_serialized_data() -
     269             :  *
     270             :  * Read auxiliary data (descriptions) for custom statistics entries from
     271             :  * the secondary statistics file.  This is called while loading the statistics
     272             :  * at startup.
     273             :  *
     274             :  * See the top of test_custom_stats_var_to_serialized_data() for a
     275             :  * detailed description of the data layout read here.
     276             :  */
     277             : static bool
     278           4 : test_custom_stats_var_from_serialized_data(const PgStat_HashKey *key,
     279             :                                            const PgStatShared_Common *header,
     280             :                                            FILE *statfile)
     281             : {
     282             :     PgStatShared_CustomVarEntry *entry;
     283             :     dsa_pointer dp;
     284             :     size_t      len;
     285             :     pgoff_t     offset;
     286             :     char       *buffer;
     287             :     bool        found;
     288           4 :     uint32      magic_number = 0;
     289             :     PgStat_HashKey file_key;
     290             : 
     291             :     /* Check the magic number first, in the main file. */
     292           4 :     if (!pgstat_read_chunk_s(statfile, &magic_number))
     293             :     {
     294           0 :         elog(WARNING, "failed to read magic number from statistics file");
     295           0 :         return false;
     296             :     }
     297             : 
     298           4 :     if (magic_number != TEST_CUSTOM_VAR_MAGIC_NUMBER)
     299             :     {
     300           0 :         elog(WARNING, "found magic number %u from statistics file, should be %u",
     301             :              magic_number, TEST_CUSTOM_VAR_MAGIC_NUMBER);
     302           0 :         return false;
     303             :     }
     304             : 
     305             :     /*
     306             :      * Read the offset from the main stats file, to be able to read the
     307             :      * auxiliary data from the secondary statistics file.
     308             :      */
     309           4 :     if (!pgstat_read_chunk_s(statfile, &offset))
     310             :     {
     311           0 :         elog(WARNING, "failed to read metadata offset from statistics file");
     312           0 :         return false;
     313             :     }
     314             : 
     315             :     /* Open statistics file for reading if not already open */
     316           4 :     if (!fd_description)
     317             :     {
     318           2 :         fd_description = AllocateFile(TEST_CUSTOM_AUX_DATA_DESC, PG_BINARY_R);
     319           2 :         if (fd_description == NULL)
     320             :         {
     321           0 :             if (errno != ENOENT)
     322           0 :                 ereport(LOG,
     323             :                         (errcode_for_file_access(),
     324             :                          errmsg("could not open statistics file \"%s\" for reading: %m",
     325             :                                 TEST_CUSTOM_AUX_DATA_DESC)));
     326           0 :             pgstat_reset_of_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS);
     327           0 :             return false;
     328             :         }
     329             :     }
     330             : 
     331             :     /* Read data from the secondary statistics file, at the specified offset */
     332           4 :     if (fseeko(fd_description, offset, SEEK_SET) != 0)
     333             :     {
     334           0 :         elog(WARNING, "could not seek in file \"%s\": %m",
     335             :              TEST_CUSTOM_AUX_DATA_DESC);
     336           0 :         return false;
     337             :     }
     338             : 
     339             :     /* Read the hash key from the secondary statistics file */
     340           4 :     if (!pgstat_read_chunk_s(fd_description, &file_key))
     341             :     {
     342           0 :         elog(WARNING, "failed to read hash key from file");
     343           0 :         return false;
     344             :     }
     345             : 
     346             :     /* Check key consistency */
     347           4 :     if (file_key.kind != key->kind ||
     348           4 :         file_key.dboid != key->dboid ||
     349           4 :         file_key.objid != key->objid)
     350             :     {
     351           0 :         elog(WARNING, "found entry key %u/%u/%" PRIu64 " not matching with %u/%u/%" PRIu64,
     352             :              file_key.kind, file_key.dboid, file_key.objid,
     353             :              key->kind, key->dboid, key->objid);
     354           0 :         return false;
     355             :     }
     356             : 
     357           4 :     entry = (PgStatShared_CustomVarEntry *) header;
     358             : 
     359             :     /* Read the description length and its data */
     360           4 :     if (!pgstat_read_chunk_s(fd_description, &len))
     361             :     {
     362           0 :         elog(WARNING, "failed to read metadata length from statistics file");
     363           0 :         return false;
     364             :     }
     365             : 
     366             :     /* Handle empty descriptions */
     367           4 :     if (len == 0)
     368             :     {
     369           0 :         entry->description = InvalidDsaPointer;
     370           0 :         return true;
     371             :     }
     372             : 
     373             :     /* Initialize DSA if needed */
     374           4 :     if (!custom_stats_description_dsa)
     375           2 :         custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
     376             : 
     377           4 :     if (!custom_stats_description_dsa)
     378             :     {
     379           0 :         elog(WARNING, "could not access DSA for custom statistics descriptions");
     380           0 :         return false;
     381             :     }
     382             : 
     383           4 :     buffer = palloc(len);
     384           4 :     if (!pgstat_read_chunk(fd_description, buffer, len))
     385             :     {
     386           0 :         pfree(buffer);
     387           0 :         elog(WARNING, "failed to read description from file");
     388           0 :         return false;
     389             :     }
     390             : 
     391             :     /* Allocate space in DSA and copy the description */
     392           4 :     dp = dsa_allocate(custom_stats_description_dsa, len);
     393           4 :     memcpy(dsa_get_address(custom_stats_description_dsa, dp), buffer, len);
     394           4 :     entry->description = dp;
     395           4 :     pfree(buffer);
     396             : 
     397           4 :     return true;
     398             : }
     399             : 
     400             : /*
     401             :  * test_custom_stats_var_finish() -
     402             :  *
     403             :  * Cleanup function called at the end of statistics file operations.
     404             :  * Handles closing files and cleanup based on the operation type.
     405             :  */
     406             : static void
     407           8 : test_custom_stats_var_finish(PgStat_StatsFileOp status)
     408             : {
     409           8 :     switch (status)
     410             :     {
     411           2 :         case STATS_WRITE:
     412           2 :             if (!fd_description)
     413           0 :                 return;
     414             : 
     415           2 :             fd_description_offset = 0;
     416             : 
     417             :             /* Check for write errors and cleanup if necessary */
     418           2 :             if (ferror(fd_description))
     419             :             {
     420           0 :                 ereport(LOG,
     421             :                         (errcode_for_file_access(),
     422             :                          errmsg("could not write to file \"%s\": %m",
     423             :                                 TEST_CUSTOM_AUX_DATA_DESC)));
     424           0 :                 FreeFile(fd_description);
     425           0 :                 unlink(TEST_CUSTOM_AUX_DATA_DESC);
     426             :             }
     427           2 :             else if (FreeFile(fd_description) < 0)
     428             :             {
     429           0 :                 ereport(LOG,
     430             :                         (errcode_for_file_access(),
     431             :                          errmsg("could not close file \"%s\": %m",
     432             :                                 TEST_CUSTOM_AUX_DATA_DESC)));
     433           0 :                 unlink(TEST_CUSTOM_AUX_DATA_DESC);
     434             :             }
     435           2 :             break;
     436             : 
     437           4 :         case STATS_READ:
     438           4 :             if (fd_description)
     439           2 :                 FreeFile(fd_description);
     440             : 
     441             :             /* Remove the file after reading */
     442           4 :             elog(DEBUG2, "removing file \"%s\"", TEST_CUSTOM_AUX_DATA_DESC);
     443           4 :             unlink(TEST_CUSTOM_AUX_DATA_DESC);
     444           4 :             break;
     445             : 
     446           2 :         case STATS_DISCARD:
     447             :             {
     448             :                 int         ret;
     449             : 
     450             :                 /* Attempt to remove the file */
     451           2 :                 ret = unlink(TEST_CUSTOM_AUX_DATA_DESC);
     452           2 :                 if (ret != 0)
     453             :                 {
     454           2 :                     if (errno == ENOENT)
     455           2 :                         elog(LOG,
     456             :                              "didn't need to unlink file \"%s\" - didn't exist",
     457             :                              TEST_CUSTOM_AUX_DATA_DESC);
     458             :                     else
     459           0 :                         ereport(LOG,
     460             :                                 (errcode_for_file_access(),
     461             :                                  errmsg("could not unlink file \"%s\": %m",
     462             :                                         TEST_CUSTOM_AUX_DATA_DESC)));
     463             :                 }
     464             :                 else
     465             :                 {
     466           0 :                     ereport(LOG,
     467             :                             (errmsg_internal("unlinked file \"%s\"",
     468             :                                              TEST_CUSTOM_AUX_DATA_DESC)));
     469             :                 }
     470             :             }
     471           2 :             break;
     472             :     }
     473             : 
     474           8 :     fd_description = NULL;
     475             : }
     476             : 
     477             : /*--------------------------------------------------------------------------
     478             :  * Helper functions
     479             :  *--------------------------------------------------------------------------
     480             :  */
     481             : 
     482             : /*
     483             :  * test_custom_stats_var_fetch_entry
     484             :  *      Look up custom statistic by name
     485             :  *
     486             :  * Returns statistics entry from shared memory, or NULL if not found.
     487             :  */
     488             : static PgStat_StatCustomVarEntry *
     489          16 : test_custom_stats_var_fetch_entry(const char *stat_name)
     490             : {
     491             :     /* Fetch entry by hashed name */
     492          16 :     return (PgStat_StatCustomVarEntry *)
     493          16 :         pgstat_fetch_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS,
     494             :                            InvalidOid,
     495          16 :                            PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name));
     496             : }
     497             : 
     498             : /*--------------------------------------------------------------------------
     499             :  * SQL-callable functions
     500             :  *--------------------------------------------------------------------------
     501             :  */
     502             : 
     503             : /*
     504             :  * test_custom_stats_var_create
     505             :  *      Create new custom statistic entry
     506             :  *
     507             :  * Initializes a statistics entry with the given name and description.
     508             :  */
     509          10 : PG_FUNCTION_INFO_V1(test_custom_stats_var_create);
     510             : Datum
     511           8 : test_custom_stats_var_create(PG_FUNCTION_ARGS)
     512             : {
     513             :     PgStat_EntryRef *entry_ref;
     514             :     PgStatShared_CustomVarEntry *shared_entry;
     515           8 :     char       *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     516           8 :     char       *description = text_to_cstring(PG_GETARG_TEXT_PP(1));
     517           8 :     dsa_pointer dp = InvalidDsaPointer;
     518             :     bool        found;
     519             : 
     520             :     /* Validate name length first */
     521           8 :     if (strlen(stat_name) >= NAMEDATALEN)
     522           0 :         ereport(ERROR,
     523             :                 (errcode(ERRCODE_NAME_TOO_LONG),
     524             :                  errmsg("custom statistic name \"%s\" is too long", stat_name),
     525             :                  errdetail("Name must be less than %d characters.", NAMEDATALEN)));
     526             : 
     527             :     /* Initialize DSA and description provided */
     528           8 :     if (!custom_stats_description_dsa)
     529           8 :         custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
     530             : 
     531           8 :     if (!custom_stats_description_dsa)
     532           0 :         ereport(ERROR,
     533             :                 (errmsg("could not access DSA for custom statistics descriptions")));
     534             : 
     535             :     /* Allocate space in DSA and copy description */
     536           8 :     dp = dsa_allocate(custom_stats_description_dsa, strlen(description) + 1);
     537          16 :     memcpy(dsa_get_address(custom_stats_description_dsa, dp),
     538             :            description,
     539           8 :            strlen(description) + 1);
     540             : 
     541             :     /* Create or get existing entry */
     542           8 :     entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
     543           8 :                                             PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), true);
     544             : 
     545           8 :     if (!entry_ref)
     546           0 :         PG_RETURN_VOID();
     547             : 
     548           8 :     shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
     549             : 
     550             :     /* Zero-initialize statistics */
     551           8 :     memset(&shared_entry->stats, 0, sizeof(shared_entry->stats));
     552             : 
     553             :     /* Store description pointer */
     554           8 :     shared_entry->description = dp;
     555             : 
     556           8 :     pgstat_unlock_entry(entry_ref);
     557             : 
     558           8 :     PG_RETURN_VOID();
     559             : }
     560             : 
     561             : /*
     562             :  * test_custom_stats_var_update
     563             :  *      Increment custom statistic counter
     564             :  *
     565             :  * Increments call count in backend-local memory.  Changes are flushed
     566             :  * to shared memory by the statistics collector.
     567             :  */
     568          22 : PG_FUNCTION_INFO_V1(test_custom_stats_var_update);
     569             : Datum
     570          20 : test_custom_stats_var_update(PG_FUNCTION_ARGS)
     571             : {
     572          20 :     char       *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     573             :     PgStat_EntryRef *entry_ref;
     574             :     PgStat_StatCustomVarEntry *pending_entry;
     575             : 
     576             :     /* Get pending entry in local memory */
     577          20 :     entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
     578          20 :                                           PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), NULL);
     579             : 
     580          20 :     pending_entry = (PgStat_StatCustomVarEntry *) entry_ref->pending;
     581          20 :     pending_entry->numcalls++;
     582             : 
     583          20 :     PG_RETURN_VOID();
     584             : }
     585             : 
     586             : /*
     587             :  * test_custom_stats_var_drop
     588             :  *      Remove custom statistic entry
     589             :  *
     590             :  * Drops the named statistic from shared memory.
     591             :  */
     592           6 : PG_FUNCTION_INFO_V1(test_custom_stats_var_drop);
     593             : Datum
     594           4 : test_custom_stats_var_drop(PG_FUNCTION_ARGS)
     595             : {
     596           4 :     char       *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     597             : 
     598             :     /* Drop entry and request GC if the entry could not be freed */
     599           4 :     if (!pgstat_drop_entry(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
     600           4 :                            PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name)))
     601           0 :         pgstat_request_entry_refs_gc();
     602             : 
     603           4 :     PG_RETURN_VOID();
     604             : }
     605             : 
     606             : /*
     607             :  * test_custom_stats_var_report
     608             :  *      Retrieve custom statistic values
     609             :  *
     610             :  * Returns single row with statistic name, call count, and description if the
     611             :  * statistic exists, otherwise returns no rows.
     612             :  */
     613          18 : PG_FUNCTION_INFO_V1(test_custom_stats_var_report);
     614             : Datum
     615          24 : test_custom_stats_var_report(PG_FUNCTION_ARGS)
     616             : {
     617             :     FuncCallContext *funcctx;
     618             :     char       *stat_name;
     619             :     PgStat_StatCustomVarEntry *stat_entry;
     620             : 
     621          24 :     if (SRF_IS_FIRSTCALL())
     622             :     {
     623             :         TupleDesc   tupdesc;
     624             :         MemoryContext oldcontext;
     625             : 
     626             :         /* Initialize SRF context */
     627          16 :         funcctx = SRF_FIRSTCALL_INIT();
     628          16 :         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
     629             : 
     630             :         /* Get composite return type */
     631          16 :         if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
     632           0 :             elog(ERROR, "test_custom_stats_var_report: return type is not composite");
     633             : 
     634          16 :         funcctx->tuple_desc = BlessTupleDesc(tupdesc);
     635          16 :         funcctx->max_calls = 1; /* single row result */
     636             : 
     637          16 :         MemoryContextSwitchTo(oldcontext);
     638             :     }
     639             : 
     640          24 :     funcctx = SRF_PERCALL_SETUP();
     641             : 
     642          24 :     if (funcctx->call_cntr < funcctx->max_calls)
     643             :     {
     644             :         Datum       values[3];
     645          16 :         bool        nulls[3] = {false, false, false};
     646             :         HeapTuple   tuple;
     647             :         PgStat_EntryRef *entry_ref;
     648             :         PgStatShared_CustomVarEntry *shared_entry;
     649          16 :         char       *description = NULL;
     650             :         bool        found;
     651             : 
     652          16 :         stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     653          16 :         stat_entry = test_custom_stats_var_fetch_entry(stat_name);
     654             : 
     655             :         /* Return row only if entry exists */
     656          16 :         if (stat_entry)
     657             :         {
     658             :             /* Get entry ref to access shared entry */
     659           8 :             entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, InvalidOid,
     660           8 :                                              PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name), false, NULL);
     661             : 
     662           8 :             if (entry_ref)
     663             :             {
     664           8 :                 shared_entry = (PgStatShared_CustomVarEntry *) entry_ref->shared_stats;
     665             : 
     666             :                 /* Get description from DSA if available */
     667           8 :                 if (DsaPointerIsValid(shared_entry->description))
     668             :                 {
     669           8 :                     if (!custom_stats_description_dsa)
     670           8 :                         custom_stats_description_dsa = GetNamedDSA("test_custom_stat_dsa", &found);
     671             : 
     672           8 :                     if (custom_stats_description_dsa)
     673           8 :                         description = dsa_get_address(custom_stats_description_dsa, shared_entry->description);
     674             :                 }
     675             :             }
     676             : 
     677           8 :             values[0] = PointerGetDatum(cstring_to_text(stat_name));
     678           8 :             values[1] = Int64GetDatum(stat_entry->numcalls);
     679             : 
     680           8 :             if (description)
     681           8 :                 values[2] = PointerGetDatum(cstring_to_text(description));
     682             :             else
     683           0 :                 nulls[2] = true;
     684             : 
     685           8 :             tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
     686           8 :             SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
     687             :         }
     688             :     }
     689             : 
     690          16 :     SRF_RETURN_DONE(funcctx);
     691             : }

Generated by: LCOV version 1.16