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

Generated by: LCOV version 2.0-1