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: 76.5 % 226 173
Test Date: 2026-11-05 01:15:59 Functions: 100.0 % 23 23
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 42.7 % 150 64

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

Generated by: LCOV version 2.0-1