From 1a065c21a9a791909cd1ca752db5aaf1f814fe37 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Thu, 26 Mar 2020 20:52:26 +0100 Subject: [PATCH] Collect SLRU statistics Adds a new system view pg_stats_slru with stats about SLRU caches, and a function pg_stat_reset_slru() to reset either all counters or just counters for a single SLRU. There is no SLRU registry this patch could use, so it simply uses the SLRU name (which is also used for LWLock tranche name) as an identifier, and a predefined list of SLRU names, and an extra "others" entry for SLRUs without a dedicated entry. Presumably, the number of extensions defining their own SLRU is very small. Author: Tomas Vondra Reviewed-by: Alvaro Herrera Discussion: https://www.postgresql.org/message-id/flat/20200119143707.gyinppnigokesjok@development --- doc/src/sgml/monitoring.sgml | 97 +++++++++ src/backend/access/transam/slru.c | 23 ++ src/backend/catalog/system_views.sql | 14 ++ src/backend/postmaster/pgstat.c | 300 +++++++++++++++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 91 ++++++++ src/include/catalog/pg_proc.dat | 15 ++ src/include/pgstat.h | 65 ++++++ src/test/regress/expected/rules.out | 10 + 8 files changed, 615 insertions(+) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 270178d57e..7ba0dbee6a 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -575,6 +575,13 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser yet included in pg_stat_user_functions). + + pg_stat_slrupg_stat_slru + One row per SLRU, showing statistics of operations. See + for details. + + + @@ -3254,6 +3261,76 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i + + The pg_stat_slru view will contain + one row for each tracked SLRU cache, showing statistics about access + to cached pages. + + + + <structname>pg_stat_slru</structname> View + + + + Column + Type + Description + + + + + + name + name + name of the SLRU + + + blks_zeroed + bigint + Number of blocks zeroed during initializations + + + blks_hit + biging + Number of times disk blocks were found already in the SLRU, + so that a read was not necessary (this only includes hits in the + SLRU, not the operating system's file system cache) + + + + blks_read + bigint + Number of disk blocks read for this SLRU + + + blks_written + bigint + Number of disk blocks written for this SLRU + + + blks_exists + bigint + Number of blocks checked for existence for this SLRU + + + flushes + bigint + Number of flushes of dirty data for this SLRU + + + truncates + bigint + Number of truncates for this SLRU + + + stats_reset + timestamp with time zone + Time at which these statistics were last reset + + + +
+ The pg_stat_user_functions view will contain one row for each tracked function, showing statistics about executions of @@ -3378,6 +3455,26 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i function can be granted to others) + + + pg_stat_reset_slru(text)pg_stat_reset_slru + void + + Reset statistics either for a single SLRU or all SLRUs in the cluster + to zero (requires superuser privileges by default, but EXECUTE for this + function can be granted to others). + Calling pg_stat_reset_slru(NULL) will zero all the + counters shown in the pg_stat_slru view for + all SLRU caches. + Calling pg_stat_reset_slru(name) with names from a + predefined list (async, clog, + commit_timestamp, multixact_offset, + multixact_member, oldserxid, + pg_xact, subtrans and + other) resets counters for only that entry. + Names not included in this list are treated as other. + + diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c index d5b7a08f73..f7160dd574 100644 --- a/src/backend/access/transam/slru.c +++ b/src/backend/access/transam/slru.c @@ -286,6 +286,9 @@ SimpleLruZeroPage(SlruCtl ctl, int pageno) /* Assume this page is now the latest active page */ shared->latest_page_number = pageno; + /* update the stats counter of zeroed pages */ + pgstat_slru_count_page_zeroed(ctl); + return slotno; } @@ -403,6 +406,10 @@ SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok, } /* Otherwise, it's ready to use */ SlruRecentlyUsed(shared, slotno); + + /* update the stats counter of pages found in the SLRU */ + pgstat_slru_count_page_hit(ctl); + return slotno; } @@ -444,6 +451,10 @@ SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok, SlruReportIOError(ctl, pageno, xid); SlruRecentlyUsed(shared, slotno); + + /* update the stats counter of pages not found in SLRU */ + pgstat_slru_count_page_read(ctl); + return slotno; } } @@ -596,6 +607,9 @@ SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int pageno) bool result; off_t endpos; + /* update the stats counter of checked pages */ + pgstat_slru_count_page_exists(ctl); + SlruFileName(ctl, path, segno); fd = OpenTransientFile(path, O_RDONLY | PG_BINARY); @@ -730,6 +744,9 @@ SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruFlush fdata) char path[MAXPGPATH]; int fd = -1; + /* update the stats counter of written pages */ + pgstat_slru_count_page_written(ctl); + /* * Honor the write-WAL-before-data rule, if appropriate, so that we do not * write out data before associated WAL records. This is the same action @@ -1125,6 +1142,9 @@ SimpleLruFlush(SlruCtl ctl, bool allow_redirtied) int i; bool ok; + /* update the stats counter of flushes */ + pgstat_slru_count_flush(ctl); + /* * Find and write dirty pages */ @@ -1186,6 +1206,9 @@ SimpleLruTruncate(SlruCtl ctl, int cutoffPage) SlruShared shared = ctl->shared; int slotno; + /* update the stats counter of truncates */ + pgstat_slru_count_truncate(ctl); + /* * The cutoff point is the start of the segment containing cutoffPage. */ diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5a6dc61630..09e226f34d 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -792,6 +792,19 @@ CREATE VIEW pg_stat_replication AS JOIN pg_stat_get_wal_senders() AS W ON (S.pid = W.pid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_slru AS + SELECT + s.name, + s.blks_zeroed, + s.blks_hit, + s.blks_read, + s.blks_written, + s.blks_exists, + s.flushes, + s.truncates, + s.stats_reset + FROM pg_stat_get_slru() s; + CREATE VIEW pg_stat_wal_receiver AS SELECT s.pid, @@ -1409,6 +1422,7 @@ REVOKE EXECUTE ON FUNCTION pg_promote(boolean, integer) FROM public; REVOKE EXECUTE ON FUNCTION pg_stat_reset() FROM public; REVOKE EXECUTE ON FUNCTION pg_stat_reset_shared(text) FROM public; +REVOKE EXECUTE ON FUNCTION pg_stat_reset_slru(text) FROM public; REVOKE EXECUTE ON FUNCTION pg_stat_reset_single_table_counters(oid) FROM public; REVOKE EXECUTE ON FUNCTION pg_stat_reset_single_function_counters(oid) FROM public; diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 4763c24be9..ea0fdade58 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -141,6 +141,24 @@ char *pgstat_stat_tmpname = NULL; */ PgStat_MsgBgWriter BgWriterStats; +/* + * SLRU statistics counters (unused in other processes) stored directly in + * stats structure so it can be sent without needing to copy things around. + * We assume this inits to zeroes. + * + * There's a separte entry for each SLRU we have. The "other" entry is used + * for all SLRUs without an explicit entry (e.g. SLRUs in extensions). + */ +static char *slru_names[] = {"async", "clog", "commit_timestamp", + "multixact_offset", "multixact_member", + "oldserxid", "pg_xact", "subtrans", + "other" /* has to be last */}; + +#define SLRU_NUM_ELEMENTS (sizeof(slru_names) / sizeof(char *)) + +/* entries in the same order as slru_names */ +PgStat_MsgSLRU SLRUStats[SLRU_NUM_ELEMENTS]; + /* ---------- * Local data * ---------- @@ -255,6 +273,7 @@ static int localNumBackends = 0; */ static PgStat_ArchiverStats archiverStats; static PgStat_GlobalStats globalStats; +static PgStat_SLRUStats slruStats[SLRU_NUM_ELEMENTS]; /* * List of OIDs of databases we need to write out. If an entry is InvalidOid, @@ -297,6 +316,7 @@ static bool pgstat_db_requested(Oid databaseid); static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg); static void pgstat_send_funcstats(void); +static void pgstat_send_slru(void); static HTAB *pgstat_collect_oids(Oid catalogid, AttrNumber anum_oid); static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared); @@ -319,11 +339,13 @@ static void pgstat_recv_dropdb(PgStat_MsgDropdb *msg, int len); static void pgstat_recv_resetcounter(PgStat_MsgResetcounter *msg, int len); static void pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len); static void pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter *msg, int len); +static void pgstat_recv_resetslrucounter(PgStat_MsgResetslrucounter *msg, int len); static void pgstat_recv_autovac(PgStat_MsgAutovacStart *msg, int len); static void pgstat_recv_vacuum(PgStat_MsgVacuum *msg, int len); static void pgstat_recv_analyze(PgStat_MsgAnalyze *msg, int len); static void pgstat_recv_archiver(PgStat_MsgArchiver *msg, int len); static void pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len); +static void pgstat_recv_slru(PgStat_MsgSLRU *msg, int len); static void pgstat_recv_funcstat(PgStat_MsgFuncstat *msg, int len); static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len); static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len); @@ -907,6 +929,9 @@ pgstat_report_stat(bool force) /* Now, send function statistics */ pgstat_send_funcstats(); + + /* Finally send SLRU statistics */ + pgstat_send_slru(); } /* @@ -1372,6 +1397,30 @@ pgstat_reset_single_counter(Oid objoid, PgStat_Single_Reset_Type type) pgstat_send(&msg, sizeof(msg)); } +/* ---------- + * pgstat_reset_slru_counter() - + * + * Tell the statistics collector to reset a single SLRU counter, or all + * SLRU counters (when name is null). + * + * Permission checking for this function is managed through the normal + * GRANT system. + * ---------- + */ +void +pgstat_reset_slru_counter(const char *name) +{ + PgStat_MsgResetslrucounter msg; + + if (pgStatSock == PGINVALID_SOCKET) + return; + + pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSLRUCOUNTER); + msg.m_index = (name) ? pgstat_slru_index(name) : -1; + + pgstat_send(&msg, sizeof(msg)); +} + /* ---------- * pgstat_report_autovac() - * @@ -2622,6 +2671,23 @@ pgstat_fetch_global(void) } +/* + * --------- + * pgstat_fetch_slru() - + * + * Support function for the SQL-callable pgstat* functions. Returns + * a pointer to the slru statistics struct. + * --------- + */ +PgStat_SLRUStats * +pgstat_fetch_slru(void) +{ + backend_read_statsfile(); + + return slruStats; +} + + /* ------------------------------------------------------------ * Functions for management of the shared-memory PgBackendStatus array * ------------------------------------------------------------ @@ -4325,6 +4391,46 @@ pgstat_send_bgwriter(void) MemSet(&BgWriterStats, 0, sizeof(BgWriterStats)); } +/* ---------- + * pgstat_send_slru() - + * + * Send SLRU statistics to the collector + * ---------- + */ +static void +pgstat_send_slru(void) +{ + int i; + + /* We assume this initializes to zeroes */ + static const PgStat_MsgSLRU all_zeroes; + + for (i = 0; i < SLRU_NUM_ELEMENTS; i++) + { + /* + * This function can be called even if nothing at all has happened. In + * this case, avoid sending a completely empty message to the stats + * collector. + */ + if (memcmp(&SLRUStats[i], &all_zeroes, sizeof(PgStat_MsgSLRU)) == 0) + continue; + + /* set the SLRU type before each send */ + SLRUStats[i].m_index = i; + + /* + * Prepare and send the message + */ + pgstat_setheader(&SLRUStats[i].m_hdr, PGSTAT_MTYPE_SLRU); + pgstat_send(&SLRUStats[i], sizeof(PgStat_MsgSLRU)); + + /* + * Clear out the statistics buffer, so it can be re-used. + */ + MemSet(&SLRUStats[i], 0, sizeof(PgStat_MsgSLRU)); + } +} + /* ---------- * PgstatCollectorMain() - @@ -4493,6 +4599,11 @@ PgstatCollectorMain(int argc, char *argv[]) len); break; + case PGSTAT_MTYPE_RESETSLRUCOUNTER: + pgstat_recv_resetslrucounter(&msg.msg_resetslrucounter, + len); + break; + case PGSTAT_MTYPE_AUTOVAC_START: pgstat_recv_autovac(&msg.msg_autovacuum_start, len); break; @@ -4513,6 +4624,10 @@ PgstatCollectorMain(int argc, char *argv[]) pgstat_recv_bgwriter(&msg.msg_bgwriter, len); break; + case PGSTAT_MTYPE_SLRU: + pgstat_recv_slru(&msg.msg_slru, len); + break; + case PGSTAT_MTYPE_FUNCSTAT: pgstat_recv_funcstat(&msg.msg_funcstat, len); break; @@ -4781,6 +4896,12 @@ pgstat_write_statsfiles(bool permanent, bool allDbs) rc = fwrite(&archiverStats, sizeof(archiverStats), 1, fpout); (void) rc; /* we'll check for error with ferror */ + /* + * Write SLRU stats struct + */ + rc = fwrite(slruStats, sizeof(slruStats), 1, fpout); + (void) rc; /* we'll check for error with ferror */ + /* * Walk through the database table. */ @@ -5016,6 +5137,7 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep) int32 format_id; bool found; const char *statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : pgstat_stat_filename; + int i; /* * The tables will live in pgStatLocalContext. @@ -5038,6 +5160,7 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep) */ memset(&globalStats, 0, sizeof(globalStats)); memset(&archiverStats, 0, sizeof(archiverStats)); + memset(&slruStats, 0, sizeof(slruStats)); /* * Set the current timestamp (will be kept only in case we can't load an @@ -5046,6 +5169,13 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep) globalStats.stat_reset_timestamp = GetCurrentTimestamp(); archiverStats.stat_reset_timestamp = globalStats.stat_reset_timestamp; + /* + * Set the same reset timestamp for all SLRU items (one + * day we might allow resetting individual SLRUs). + */ + for (i = 0; i < SLRU_NUM_ELEMENTS; i++) + slruStats[i].stat_reset_timestamp = globalStats.stat_reset_timestamp; + /* * Try to open the stats file. If it doesn't exist, the backends simply * return zero for anything and the collector simply starts from scratch @@ -5108,6 +5238,17 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep) goto done; } + /* + * Read SLRU stats struct + */ + if (fread(slruStats, 1, sizeof(slruStats), fpin) != sizeof(slruStats)) + { + ereport(pgStatRunningInCollector ? LOG : WARNING, + (errmsg("corrupted statistics file \"%s\"", statfile))); + memset(&slruStats, 0, sizeof(slruStats)); + goto done; + } + /* * We found an existing collector stats file. Read it and put all the * hashtable entries into place. @@ -5406,9 +5547,11 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent, PgStat_StatDBEntry dbentry; PgStat_GlobalStats myGlobalStats; PgStat_ArchiverStats myArchiverStats; + PgStat_SLRUStats mySLRUStats; FILE *fpin; int32 format_id; const char *statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : pgstat_stat_filename; + int i; /* * Try to open the stats file. As above, anything but ENOENT is worthy of @@ -5460,6 +5603,21 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool permanent, return false; } + /* + * Read SLRU stats struct + */ + for (i = 0; i < SLRU_NUM_ELEMENTS; i++) + { + if (fread(&mySLRUStats, 1, sizeof(PgStat_SLRUStats), + fpin) != sizeof(PgStat_SLRUStats)) + { + ereport(pgStatRunningInCollector ? LOG : WARNING, + (errmsg("corrupted statistics file \"%s\"", statfile))); + FreeFile(fpin); + return false; + } + } + /* By default, we're going to return the timestamp of the global file. */ *ts = myGlobalStats.stats_timestamp; @@ -6057,6 +6215,32 @@ pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter *msg, int len) HASH_REMOVE, NULL); } +/* ---------- + * pgstat_recv_resetslrucounter() - + * + * Reset some SLRU statistics of the cluster. + * ---------- + */ +static void +pgstat_recv_resetslrucounter(PgStat_MsgResetslrucounter *msg, int len) +{ + int i; + TimestampTz ts = GetCurrentTimestamp(); + + memset(&slruStats, 0, sizeof(slruStats)); + + elog(LOG, "msg->m_index = %d", msg->m_index); + + for (i = 0; i < SLRU_NUM_ELEMENTS; i++) + { + if ((msg->m_index == -1) || (msg->m_index == i)) + { + memset(&slruStats[i], 0, sizeof(slruStats[i])); + slruStats[i].stat_reset_timestamp = ts; + } + } +} + /* ---------- * pgstat_recv_autovac() - * @@ -6201,6 +6385,24 @@ pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len) globalStats.buf_alloc += msg->m_buf_alloc; } +/* ---------- + * pgstat_recv_slru() - + * + * Process a SLRU message. + * ---------- + */ +static void +pgstat_recv_slru(PgStat_MsgSLRU *msg, int len) +{ + slruStats[msg->m_index].blocks_zeroed += msg->m_blocks_zeroed; + slruStats[msg->m_index].blocks_hit += msg->m_blocks_hit; + slruStats[msg->m_index].blocks_read += msg->m_blocks_read; + slruStats[msg->m_index].blocks_written += msg->m_blocks_written; + slruStats[msg->m_index].blocks_exists += msg->m_blocks_exists; + slruStats[msg->m_index].flush += msg->m_flush; + slruStats[msg->m_index].truncate += msg->m_truncate; +} + /* ---------- * pgstat_recv_recoveryconflict() - * @@ -6455,3 +6657,101 @@ pgstat_clip_activity(const char *raw_activity) return activity; } + +/* + * pgstat_slru_index + * + * Determine index of entry for a SLRU with a given name. If there's no exact + * match, returns index of the last "other" entry used for SLRUs defined in + * external proejcts. + */ +int +pgstat_slru_index(const char *name) +{ + int i; + + for (i = 0; i < SLRU_NUM_ELEMENTS; i++) + { + if (strcmp(slru_names[i], name) == 0) + return i; + } + + /* return index of the last entry (which is the "other" one) */ + return (SLRU_NUM_ELEMENTS - 1); +} + +/* + * pgstat_slru_name + * + * Returns SLRU name for an index. The index may be above SLRU_NUM_ELEMENTS, + * in which case this returns NULL. This allows writing code that does not + * know the number of entries in advance. + */ +char * +pgstat_slru_name(int idx) +{ + Assert(idx >= 0); + + if (idx >= SLRU_NUM_ELEMENTS) + return NULL; + + return slru_names[idx]; +} + +/* + * slru_entry + * + * Returns pointer to entry with counters for given SLRU (based on the name + * stored in SlruCtl as lwlock tranche name). + */ +static PgStat_MsgSLRU * +slru_entry(SlruCtl ctl) +{ + int idx = pgstat_slru_index(ctl->shared->lwlock_tranche_name); + + Assert((idx >= 0) && (idx < SLRU_NUM_ELEMENTS)); + + return &SLRUStats[idx]; +} + +void +pgstat_slru_count_page_zeroed(SlruCtl ctl) +{ + slru_entry(ctl)->m_blocks_zeroed += 1; +} + +void +pgstat_slru_count_page_hit(SlruCtl ctl) +{ + slru_entry(ctl)->m_blocks_hit += 1; +} + +void +pgstat_slru_count_page_exists(SlruCtl ctl) +{ + slru_entry(ctl)->m_blocks_exists += 1; +} + +void +pgstat_slru_count_page_read(SlruCtl ctl) +{ + slru_entry(ctl)->m_blocks_read += 1; +} + +void +pgstat_slru_count_page_written(SlruCtl ctl) +{ + slru_entry(ctl)->m_blocks_written += 1; +} + +void +pgstat_slru_count_flush(SlruCtl ctl) +{ + slru_entry(ctl)->m_flush += 1; +} + +void +pgstat_slru_count_truncate(SlruCtl ctl) +{ + slru_entry(ctl)->m_truncate += 1; +} diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index cea01534a5..99b20de773 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -1674,6 +1674,83 @@ pg_stat_get_buf_alloc(PG_FUNCTION_ARGS) PG_RETURN_INT64(pgstat_fetch_global()->buf_alloc); } +/* + * Returns statistics of SLRU caches. + */ +Datum +pg_stat_get_slru(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_SLRU_COLS 9 + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + int i; + PgStat_SLRUStats *stats; + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + /* request SLRU stats from the stat collector */ + stats = pgstat_fetch_slru(); + + for (i = 0; ; i++) + { + /* for each row */ + Datum values[PG_STAT_GET_SLRU_COLS]; + bool nulls[PG_STAT_GET_SLRU_COLS]; + PgStat_SLRUStats stat = stats[i]; + char *name; + + name = pgstat_slru_name(i); + + if (!name) + break; + + MemSet(values, 0, sizeof(values)); + MemSet(nulls, 0, sizeof(nulls)); + + values[0] = PointerGetDatum(cstring_to_text(name)); + values[1] = Int64GetDatum(stat.blocks_zeroed); + values[2] = Int64GetDatum(stat.blocks_hit); + values[3] = Int64GetDatum(stat.blocks_read); + values[4] = Int64GetDatum(stat.blocks_written); + values[5] = Int64GetDatum(stat.blocks_exists); + values[6] = Int64GetDatum(stat.flush); + values[7] = Int64GetDatum(stat.truncate); + values[8] = Int64GetDatum(stat.stat_reset_timestamp); + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + + /* clean up and return the tuplestore */ + tuplestore_donestoring(tupstore); + + return (Datum) 0; +} + Datum pg_stat_get_xact_numscans(PG_FUNCTION_ARGS) { @@ -1919,6 +1996,20 @@ pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +/* Reset SLRU counters (a specific one or all of them). */ +Datum +pg_stat_reset_slru(PG_FUNCTION_ARGS) +{ + char *target = NULL; + + if (!PG_ARGISNULL(0)) + target = text_to_cstring(PG_GETARG_TEXT_PP(0)); + + pgstat_reset_slru_counter(target); + + PG_RETURN_VOID(); +} + Datum pg_stat_get_archiver(PG_FUNCTION_ARGS) { diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 87d25d4a4b..96a93d8570 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5431,6 +5431,16 @@ proname => 'pg_stat_get_buf_alloc', provolatile => 's', proparallel => 'r', prorettype => 'int8', proargtypes => '', prosrc => 'pg_stat_get_buf_alloc' }, +{ oid => '8614', + descr => 'statistics: information about SLRU caches', + proname => 'pg_stat_get_slru', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => '', + proallargtypes => '{text,int8,int8,int8,int8,int8,int8,int8,timestamptz}', + proargmodes => '{o,o,o,o,o,o,o,o,o}', + proargnames => '{name,blks_zeroed,blks_hit,blks_read,blks_written,blks_exists,flushes,truncates,stats_reset}', + prosrc => 'pg_stat_get_slru' }, + { oid => '2978', descr => 'statistics: number of function calls', proname => 'pg_stat_get_function_calls', provolatile => 's', proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', @@ -5535,6 +5545,11 @@ proname => 'pg_stat_reset_single_function_counters', provolatile => 'v', prorettype => 'void', proargtypes => 'oid', prosrc => 'pg_stat_reset_single_function_counters' }, +{ oid => '4179', + descr => 'statistics: reset collected statistics for a single SLRU', + proname => 'pg_stat_reset_slru', provolatile => 'v', proisstrict => 'f', + prorettype => 'void', proargtypes => 'text', + prosrc => 'pg_stat_reset_slru' }, { oid => '3163', descr => 'current trigger depth', proname => 'pg_trigger_depth', provolatile => 's', proparallel => 'r', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index a07012bf4b..0fac192ea1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -11,6 +11,7 @@ #ifndef PGSTAT_H #define PGSTAT_H +#include "access/slru.h" #include "datatype/timestamp.h" #include "libpq/pqcomm.h" #include "miscadmin.h" @@ -55,11 +56,13 @@ typedef enum StatMsgType PGSTAT_MTYPE_RESETCOUNTER, PGSTAT_MTYPE_RESETSHAREDCOUNTER, PGSTAT_MTYPE_RESETSINGLECOUNTER, + PGSTAT_MTYPE_RESETSLRUCOUNTER, PGSTAT_MTYPE_AUTOVAC_START, PGSTAT_MTYPE_VACUUM, PGSTAT_MTYPE_ANALYZE, PGSTAT_MTYPE_ARCHIVER, PGSTAT_MTYPE_BGWRITER, + PGSTAT_MTYPE_SLRU, PGSTAT_MTYPE_FUNCSTAT, PGSTAT_MTYPE_FUNCPURGE, PGSTAT_MTYPE_RECOVERYCONFLICT, @@ -343,6 +346,17 @@ typedef struct PgStat_MsgResetsinglecounter Oid m_objectid; } PgStat_MsgResetsinglecounter; +/* ---------- + * PgStat_MsgResetslrucounter Sent by the backend to tell the collector + * to reset a SLRU counter + * ---------- + */ +typedef struct PgStat_MsgResetslrucounter +{ + PgStat_MsgHdr m_hdr; + int m_index; +} PgStat_MsgResetslrucounter; + /* ---------- * PgStat_MsgAutovacStart Sent by the autovacuum daemon to signal * that a database is going to be processed @@ -423,6 +437,23 @@ typedef struct PgStat_MsgBgWriter PgStat_Counter m_checkpoint_sync_time; } PgStat_MsgBgWriter; +/* ---------- + * PgStat_MsgSLRU Sent by the SLRU to update statistics. + * ---------- + */ +typedef struct PgStat_MsgSLRU +{ + PgStat_MsgHdr m_hdr; + PgStat_Counter m_index; + PgStat_Counter m_blocks_zeroed; + PgStat_Counter m_blocks_hit; + PgStat_Counter m_blocks_read; + PgStat_Counter m_blocks_written; + PgStat_Counter m_blocks_exists; + PgStat_Counter m_flush; + PgStat_Counter m_truncate; +} PgStat_MsgSLRU; + /* ---------- * PgStat_MsgRecoveryConflict Sent by the backend upon recovery conflict * ---------- @@ -560,11 +591,13 @@ typedef union PgStat_Msg PgStat_MsgResetcounter msg_resetcounter; PgStat_MsgResetsharedcounter msg_resetsharedcounter; PgStat_MsgResetsinglecounter msg_resetsinglecounter; + PgStat_MsgResetslrucounter msg_resetslrucounter; PgStat_MsgAutovacStart msg_autovacuum_start; PgStat_MsgVacuum msg_vacuum; PgStat_MsgAnalyze msg_analyze; PgStat_MsgArchiver msg_archiver; PgStat_MsgBgWriter msg_bgwriter; + PgStat_MsgSLRU msg_slru; PgStat_MsgFuncstat msg_funcstat; PgStat_MsgFuncpurge msg_funcpurge; PgStat_MsgRecoveryConflict msg_recoveryconflict; @@ -712,6 +745,21 @@ typedef struct PgStat_GlobalStats TimestampTz stat_reset_timestamp; } PgStat_GlobalStats; +/* + * SLRU statistics kept in the stats collector + */ +typedef struct PgStat_SLRUStats +{ + PgStat_Counter blocks_zeroed; + PgStat_Counter blocks_hit; + PgStat_Counter blocks_read; + PgStat_Counter blocks_written; + PgStat_Counter blocks_exists; + PgStat_Counter flush; + PgStat_Counter truncate; + TimestampTz stat_reset_timestamp; +} PgStat_SLRUStats; + /* ---------- * Backend states @@ -1209,6 +1257,11 @@ extern char *pgstat_stat_filename; */ extern PgStat_MsgBgWriter BgWriterStats; +/* + * SLRU statistics counters are updated directly by slru. + */ +extern PgStat_MsgSLRU SlruStats[]; + /* * Updated by pgstat_count_buffer_*_time macros */ @@ -1246,6 +1299,7 @@ extern void pgstat_clear_snapshot(void); extern void pgstat_reset_counters(void); extern void pgstat_reset_shared_counters(const char *); extern void pgstat_reset_single_counter(Oid objectid, PgStat_Single_Reset_Type type); +extern void pgstat_reset_slru_counter(const char *); extern void pgstat_report_autovac(Oid dboid); extern void pgstat_report_vacuum(Oid tableoid, bool shared, @@ -1421,5 +1475,16 @@ extern PgStat_StatFuncEntry *pgstat_fetch_stat_funcentry(Oid funcid); extern int pgstat_fetch_stat_numbackends(void); extern PgStat_ArchiverStats *pgstat_fetch_stat_archiver(void); extern PgStat_GlobalStats *pgstat_fetch_global(void); +extern PgStat_SLRUStats *pgstat_fetch_slru(void); + +extern void pgstat_slru_count_page_zeroed(SlruCtl ctl); +extern void pgstat_slru_count_page_hit(SlruCtl ctl); +extern void pgstat_slru_count_page_read(SlruCtl ctl); +extern void pgstat_slru_count_page_written(SlruCtl ctl); +extern void pgstat_slru_count_page_exists(SlruCtl ctl); +extern void pgstat_slru_count_flush(SlruCtl ctl); +extern void pgstat_slru_count_truncate(SlruCtl ctl); +extern char *pgstat_slru_name(int idx); +extern int pgstat_slru_index(const char *name); #endif /* PGSTAT_H */ diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a2077bbad4..798364230e 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2006,6 +2006,16 @@ pg_stat_replication| SELECT s.pid, FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid) JOIN pg_stat_get_wal_senders() w(pid, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag, sync_priority, sync_state, reply_time, spill_txns, spill_count, spill_bytes) ON ((s.pid = w.pid))) LEFT JOIN pg_authid u ON ((s.usesysid = u.oid))); +pg_stat_slru| SELECT s.name, + s.blks_zeroed, + s.blks_hit, + s.blks_read, + s.blks_written, + s.blks_exists, + s.flushes, + s.truncates, + s.stats_reset + FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, blks_written, blks_exists, flushes, truncates, stats_reset); pg_stat_ssl| SELECT s.pid, s.ssl, s.sslversion AS version, -- 2.21.1