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 20devel Lines: 74.1 % 201 149
Test Date: 2026-07-25 22:15:46 Functions: 100.0 % 15 15
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 42.1 % 140 59

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

Generated by: LCOV version 2.0-1