pgstat: split different types of stats into separate files.

pgstat.c is very long, and it's hard to find an order that makes sense and is
likely to be maintained over time. Splitting the different pieces into
separate files makes that a lot easier.

With a few exceptions, this commit just moves code around. Those exceptions
are:
- adding file headers for new files
- removing 'static' from functions
- adapting pgstat_assert_is_up() to work across TUs
- minor comment adjustments
git diff --color-moved=dimmed-zebra is very helpful separating code movement
from code changes.

The next commit in this series will reorder pgstat.[ch] contents to be a bit
more coherent.

Earlier revisions of this patch had "global" statistics (archiver, bgwriter,
checkpointer, replication slots, SLRU, WAL) in one file, because each seemed
small enough. However later commits will increase their size and their
aggregate size is not insubstantial. It also just seems easier to split each
type of statistic into its own file.

Author: Andres Freund <andres@anarazel.de>
Discussion: https://postgr.es/m/20220303021600.hs34ghqcw6zcokdh@alap3.anarazel.de
This commit is contained in:
Andres Freund 2022-03-21 12:02:25 -07:00
parent cb02fcb4c9
commit 13619598f1
13 changed files with 2371 additions and 2003 deletions

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,16 @@ include $(top_builddir)/src/Makefile.global
OBJS = \
backend_progress.o \
backend_status.o \
pgstat_archiver.o \
pgstat_bgwriter.o \
pgstat_checkpointer.o \
pgstat_database.o \
pgstat_function.o \
pgstat_relation.o \
pgstat_replslot.o \
pgstat_subscription.o \
pgstat_wal.o \
pgstat_slru.o \
wait_event.o
include $(top_srcdir)/src/backend/common.mk

View File

@ -0,0 +1,44 @@
/* -------------------------------------------------------------------------
*
* pgstat_archiver.c
* Implementation of archiver statistics.
*
* This file contains the implementation of archiver statistics. It is kept
* separate from pgstat.c to enforce the line between the statistics access /
* storage implementation and the details about individual types of
* statistics.
*
* Copyright (c) 2001-2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/utils/activity/pgstat_archiver.c
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "utils/pgstat_internal.h"
#include "utils/timestamp.h"
/* ----------
* pgstat_send_archiver() -
*
* Tell the collector about the WAL file that we successfully
* archived or failed to archive.
* ----------
*/
void
pgstat_send_archiver(const char *xlog, bool failed)
{
PgStat_MsgArchiver msg;
/*
* Prepare and send the message
*/
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_ARCHIVER);
msg.m_failed = failed;
strlcpy(msg.m_xlog, xlog, sizeof(msg.m_xlog));
msg.m_timestamp = GetCurrentTimestamp();
pgstat_send(&msg, sizeof(msg));
}

View File

@ -0,0 +1,63 @@
/* -------------------------------------------------------------------------
*
* pgstat_bgwriter.c
* Implementation of bgwriter statistics.
*
* This file contains the implementation of bgwriter statistics. It is kept
* separate from pgstat.c to enforce the line between the statistics access /
* storage implementation and the details about individual types of
* statistics.
*
* Copyright (c) 2001-2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/utils/activity/pgstat_bgwriter.c
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "utils/pgstat_internal.h"
/*
* BgWriter global statistics counters. Stored directly in a stats
* message structure so they can be sent without needing to copy things
* around. We assume this init to zeroes.
*/
PgStat_MsgBgWriter PendingBgWriterStats;
/* ----------
* pgstat_send_bgwriter() -
*
* Send bgwriter statistics to the collector
* ----------
*/
void
pgstat_send_bgwriter(void)
{
/* We assume this initializes to zeroes */
static const PgStat_MsgBgWriter all_zeroes;
pgstat_assert_is_up();
/*
* 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(&PendingBgWriterStats, &all_zeroes, sizeof(PgStat_MsgBgWriter)) == 0)
return;
/*
* Prepare and send the message
*/
pgstat_setheader(&PendingBgWriterStats.m_hdr, PGSTAT_MTYPE_BGWRITER);
pgstat_send(&PendingBgWriterStats, sizeof(PendingBgWriterStats));
/*
* Clear out the statistics buffer, so it can be re-used.
*/
MemSet(&PendingBgWriterStats, 0, sizeof(PendingBgWriterStats));
}

View File

@ -0,0 +1,61 @@
/* -------------------------------------------------------------------------
*
* pgstat_checkpointer.c
* Implementation of checkpoint statistics.
*
* This file contains the implementation of checkpoint statistics. It is kept
* separate from pgstat.c to enforce the line between the statistics access /
* storage implementation and the details about individual types of
* statistics.
*
* Copyright (c) 2001-2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/utils/activity/pgstat_checkpointer.c
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "utils/pgstat_internal.h"
/*
* Checkpointer global statistics counters. Stored directly in a stats
* message structure so they can be sent without needing to copy things
* around. We assume this init to zeroes.
*/
PgStat_MsgCheckpointer PendingCheckpointerStats;
/* ----------
* pgstat_send_checkpointer() -
*
* Send checkpointer statistics to the collector
* ----------
*/
void
pgstat_send_checkpointer(void)
{
/* We assume this initializes to zeroes */
static const PgStat_MsgCheckpointer all_zeroes;
/*
* 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(&PendingCheckpointerStats, &all_zeroes, sizeof(PgStat_MsgCheckpointer)) == 0)
return;
/*
* Prepare and send the message
*/
pgstat_setheader(&PendingCheckpointerStats.m_hdr, PGSTAT_MTYPE_CHECKPOINTER);
pgstat_send(&PendingCheckpointerStats, sizeof(PendingCheckpointerStats));
/*
* Clear out the statistics buffer, so it can be re-used.
*/
MemSet(&PendingCheckpointerStats, 0, sizeof(PendingCheckpointerStats));
}

View File

@ -0,0 +1,279 @@
/* -------------------------------------------------------------------------
*
* pgstat_database.c
* Implementation of database statistics.
*
* This file contains the implementation of database statistics. It is kept
* separate from pgstat.c to enforce the line between the statistics access /
* storage implementation and the details about individual types of
* statistics.
*
* Copyright (c) 2001-2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/utils/activity/pgstat_database.c
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "utils/pgstat_internal.h"
#include "utils/timestamp.h"
static bool pgstat_should_report_connstat(void);
int pgStatXactCommit = 0;
int pgStatXactRollback = 0;
PgStat_Counter pgStatBlockReadTime = 0;
PgStat_Counter pgStatBlockWriteTime = 0;
PgStat_Counter pgStatActiveTime = 0;
PgStat_Counter pgStatTransactionIdleTime = 0;
SessionEndType pgStatSessionEndCause = DISCONNECT_NORMAL;
static PgStat_Counter pgLastSessionReportTime = 0;
/* ----------
* pgstat_drop_database() -
*
* Tell the collector that we just dropped a database.
* (If the message gets lost, we will still clean the dead DB eventually
* via future invocations of pgstat_vacuum_stat().)
* ----------
*/
void
pgstat_drop_database(Oid databaseid)
{
PgStat_MsgDropdb msg;
if (pgStatSock == PGINVALID_SOCKET)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DROPDB);
msg.m_databaseid = databaseid;
pgstat_send(&msg, sizeof(msg));
}
/* --------
* pgstat_report_recovery_conflict() -
*
* Tell the collector about a Hot Standby recovery conflict.
* --------
*/
void
pgstat_report_recovery_conflict(int reason)
{
PgStat_MsgRecoveryConflict msg;
if (pgStatSock == PGINVALID_SOCKET || !pgstat_track_counts)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RECOVERYCONFLICT);
msg.m_databaseid = MyDatabaseId;
msg.m_reason = reason;
pgstat_send(&msg, sizeof(msg));
}
/* --------
* pgstat_report_deadlock() -
*
* Tell the collector about a deadlock detected.
* --------
*/
void
pgstat_report_deadlock(void)
{
PgStat_MsgDeadlock msg;
if (pgStatSock == PGINVALID_SOCKET || !pgstat_track_counts)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DEADLOCK);
msg.m_databaseid = MyDatabaseId;
pgstat_send(&msg, sizeof(msg));
}
/* --------
* pgstat_report_checksum_failures_in_db() -
*
* Tell the collector about one or more checksum failures.
* --------
*/
void
pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount)
{
PgStat_MsgChecksumFailure msg;
if (pgStatSock == PGINVALID_SOCKET || !pgstat_track_counts)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_CHECKSUMFAILURE);
msg.m_databaseid = dboid;
msg.m_failurecount = failurecount;
msg.m_failure_time = GetCurrentTimestamp();
pgstat_send(&msg, sizeof(msg));
}
/* --------
* pgstat_report_checksum_failure() -
*
* Tell the collector about a checksum failure.
* --------
*/
void
pgstat_report_checksum_failure(void)
{
pgstat_report_checksum_failures_in_db(MyDatabaseId, 1);
}
/* --------
* pgstat_report_tempfile() -
*
* Tell the collector about a temporary file.
* --------
*/
void
pgstat_report_tempfile(size_t filesize)
{
PgStat_MsgTempFile msg;
if (pgStatSock == PGINVALID_SOCKET || !pgstat_track_counts)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TEMPFILE);
msg.m_databaseid = MyDatabaseId;
msg.m_filesize = filesize;
pgstat_send(&msg, sizeof(msg));
}
/* --------
* pgstat_report_connect() -
*
* Tell the collector about a new connection.
* --------
*/
void
pgstat_report_connect(Oid dboid)
{
PgStat_MsgConnect msg;
if (!pgstat_should_report_connstat())
return;
pgLastSessionReportTime = MyStartTimestamp;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_CONNECT);
msg.m_databaseid = MyDatabaseId;
pgstat_send(&msg, sizeof(PgStat_MsgConnect));
}
/* --------
* pgstat_report_disconnect() -
*
* Tell the collector about a disconnect.
* --------
*/
void
pgstat_report_disconnect(Oid dboid)
{
PgStat_MsgDisconnect msg;
if (!pgstat_should_report_connstat())
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DISCONNECT);
msg.m_databaseid = MyDatabaseId;
msg.m_cause = pgStatSessionEndCause;
pgstat_send(&msg, sizeof(PgStat_MsgDisconnect));
}
void
AtEOXact_PgStat_Database(bool isCommit, bool parallel)
{
/* Don't count parallel worker transaction stats */
if (!parallel)
{
/*
* Count transaction commit or abort. (We use counters, not just
* bools, in case the reporting message isn't sent right away.)
*/
if (isCommit)
pgStatXactCommit++;
else
pgStatXactRollback++;
}
}
/*
* Subroutine for pgstat_send_tabstat: Handle xact commit/rollback and I/O
* timings.
*/
void
pgstat_update_dbstats(PgStat_MsgTabstat *tsmsg, TimestampTz now)
{
if (OidIsValid(tsmsg->m_databaseid))
{
tsmsg->m_xact_commit = pgStatXactCommit;
tsmsg->m_xact_rollback = pgStatXactRollback;
tsmsg->m_block_read_time = pgStatBlockReadTime;
tsmsg->m_block_write_time = pgStatBlockWriteTime;
if (pgstat_should_report_connstat())
{
long secs;
int usecs;
/*
* pgLastSessionReportTime is initialized to MyStartTimestamp by
* pgstat_report_connect().
*/
TimestampDifference(pgLastSessionReportTime, now, &secs, &usecs);
pgLastSessionReportTime = now;
tsmsg->m_session_time = (PgStat_Counter) secs * 1000000 + usecs;
tsmsg->m_active_time = pgStatActiveTime;
tsmsg->m_idle_in_xact_time = pgStatTransactionIdleTime;
}
else
{
tsmsg->m_session_time = 0;
tsmsg->m_active_time = 0;
tsmsg->m_idle_in_xact_time = 0;
}
pgStatXactCommit = 0;
pgStatXactRollback = 0;
pgStatBlockReadTime = 0;
pgStatBlockWriteTime = 0;
pgStatActiveTime = 0;
pgStatTransactionIdleTime = 0;
}
else
{
tsmsg->m_xact_commit = 0;
tsmsg->m_xact_rollback = 0;
tsmsg->m_block_read_time = 0;
tsmsg->m_block_write_time = 0;
tsmsg->m_session_time = 0;
tsmsg->m_active_time = 0;
tsmsg->m_idle_in_xact_time = 0;
}
}
/* --------
* pgstat_should_report_connstats() -
*
* We report session statistics only for normal backend processes. Parallel
* workers run in parallel, so they don't contribute to session times, even
* though they use CPU time. Walsender processes could be considered here,
* but they have different session characteristics from normal backends (for
* example, they are always "active"), so they would skew session statistics.
* ----------
*/
static bool
pgstat_should_report_connstat(void)
{
return MyBackendType == B_BACKEND;
}

View File

@ -0,0 +1,225 @@
/* -------------------------------------------------------------------------
*
* pgstat_function.c
* Implementation of function statistics.
*
* This file contains the implementation of function statistics. It is kept
* separate from pgstat.c to enforce the line between the statistics access /
* storage implementation and the details about individual types of
* statistics.
*
* Copyright (c) 2001-2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/utils/activity/pgstat_function.c
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "utils/pgstat_internal.h"
#include "utils/timestamp.h"
/* ----------
* GUC parameters
* ----------
*/
int pgstat_track_functions = TRACK_FUNC_OFF;
/*
* Indicates if backend has some function stats that it hasn't yet
* sent to the collector.
*/
bool have_function_stats = false;
/*
* Backends store per-function info that's waiting to be sent to the collector
* in this hash table (indexed by function OID).
*/
static HTAB *pgStatFunctions = NULL;
/*
* Total time charged to functions so far in the current backend.
* We use this to help separate "self" and "other" time charges.
* (We assume this initializes to zero.)
*/
static instr_time total_func_time;
/*
* Initialize function call usage data.
* Called by the executor before invoking a function.
*/
void
pgstat_init_function_usage(FunctionCallInfo fcinfo,
PgStat_FunctionCallUsage *fcu)
{
PgStat_BackendFunctionEntry *htabent;
bool found;
if (pgstat_track_functions <= fcinfo->flinfo->fn_stats)
{
/* stats not wanted */
fcu->fs = NULL;
return;
}
if (!pgStatFunctions)
{
/* First time through - initialize function stat table */
HASHCTL hash_ctl;
hash_ctl.keysize = sizeof(Oid);
hash_ctl.entrysize = sizeof(PgStat_BackendFunctionEntry);
pgStatFunctions = hash_create("Function stat entries",
PGSTAT_FUNCTION_HASH_SIZE,
&hash_ctl,
HASH_ELEM | HASH_BLOBS);
}
/* Get the stats entry for this function, create if necessary */
htabent = hash_search(pgStatFunctions, &fcinfo->flinfo->fn_oid,
HASH_ENTER, &found);
if (!found)
MemSet(&htabent->f_counts, 0, sizeof(PgStat_FunctionCounts));
fcu->fs = &htabent->f_counts;
/* save stats for this function, later used to compensate for recursion */
fcu->save_f_total_time = htabent->f_counts.f_total_time;
/* save current backend-wide total time */
fcu->save_total = total_func_time;
/* get clock time as of function start */
INSTR_TIME_SET_CURRENT(fcu->f_start);
}
/*
* Calculate function call usage and update stat counters.
* Called by the executor after invoking a function.
*
* In the case of a set-returning function that runs in value-per-call mode,
* we will see multiple pgstat_init_function_usage/pgstat_end_function_usage
* calls for what the user considers a single call of the function. The
* finalize flag should be TRUE on the last call.
*/
void
pgstat_end_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize)
{
PgStat_FunctionCounts *fs = fcu->fs;
instr_time f_total;
instr_time f_others;
instr_time f_self;
/* stats not wanted? */
if (fs == NULL)
return;
/* total elapsed time in this function call */
INSTR_TIME_SET_CURRENT(f_total);
INSTR_TIME_SUBTRACT(f_total, fcu->f_start);
/* self usage: elapsed minus anything already charged to other calls */
f_others = total_func_time;
INSTR_TIME_SUBTRACT(f_others, fcu->save_total);
f_self = f_total;
INSTR_TIME_SUBTRACT(f_self, f_others);
/* update backend-wide total time */
INSTR_TIME_ADD(total_func_time, f_self);
/*
* Compute the new f_total_time as the total elapsed time added to the
* pre-call value of f_total_time. This is necessary to avoid
* double-counting any time taken by recursive calls of myself. (We do
* not need any similar kluge for self time, since that already excludes
* any recursive calls.)
*/
INSTR_TIME_ADD(f_total, fcu->save_f_total_time);
/* update counters in function stats table */
if (finalize)
fs->f_numcalls++;
fs->f_total_time = f_total;
INSTR_TIME_ADD(fs->f_self_time, f_self);
/* indicate that we have something to send */
have_function_stats = true;
}
/*
* Subroutine for pgstat_report_stat: populate and send a function stat message
*/
void
pgstat_send_funcstats(void)
{
/* we assume this inits to all zeroes: */
static const PgStat_FunctionCounts all_zeroes;
PgStat_MsgFuncstat msg;
PgStat_BackendFunctionEntry *entry;
HASH_SEQ_STATUS fstat;
if (pgStatFunctions == NULL)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_FUNCSTAT);
msg.m_databaseid = MyDatabaseId;
msg.m_nentries = 0;
hash_seq_init(&fstat, pgStatFunctions);
while ((entry = (PgStat_BackendFunctionEntry *) hash_seq_search(&fstat)) != NULL)
{
PgStat_FunctionEntry *m_ent;
/* Skip it if no counts accumulated since last time */
if (memcmp(&entry->f_counts, &all_zeroes,
sizeof(PgStat_FunctionCounts)) == 0)
continue;
/* need to convert format of time accumulators */
m_ent = &msg.m_entry[msg.m_nentries];
m_ent->f_id = entry->f_id;
m_ent->f_numcalls = entry->f_counts.f_numcalls;
m_ent->f_total_time = INSTR_TIME_GET_MICROSEC(entry->f_counts.f_total_time);
m_ent->f_self_time = INSTR_TIME_GET_MICROSEC(entry->f_counts.f_self_time);
if (++msg.m_nentries >= PGSTAT_NUM_FUNCENTRIES)
{
pgstat_send(&msg, offsetof(PgStat_MsgFuncstat, m_entry[0]) +
msg.m_nentries * sizeof(PgStat_FunctionEntry));
msg.m_nentries = 0;
}
/* reset the entry's counts */
MemSet(&entry->f_counts, 0, sizeof(PgStat_FunctionCounts));
}
if (msg.m_nentries > 0)
pgstat_send(&msg, offsetof(PgStat_MsgFuncstat, m_entry[0]) +
msg.m_nentries * sizeof(PgStat_FunctionEntry));
have_function_stats = false;
}
/*
* find_funcstat_entry - find any existing PgStat_BackendFunctionEntry entry
* for specified function
*
* If no entry, return NULL, don't create a new one
*/
PgStat_BackendFunctionEntry *
find_funcstat_entry(Oid func_id)
{
pgstat_assert_is_up();
if (pgStatFunctions == NULL)
return NULL;
return (PgStat_BackendFunctionEntry *) hash_search(pgStatFunctions,
(void *) &func_id,
HASH_FIND, NULL);
}

View File

@ -0,0 +1,935 @@
/* -------------------------------------------------------------------------
*
* pgstat_relation.c
* Implementation of relation statistics.
*
* This file contains the implementation of function relation. It is kept
* separate from pgstat.c to enforce the line between the statistics access /
* storage implementation and the details about individual types of
* statistics.
*
* Copyright (c) 2001-2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/utils/activity/pgstat_relation.c
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/twophase_rmgr.h"
#include "access/xact.h"
#include "postmaster/autovacuum.h"
#include "utils/memutils.h"
#include "utils/pgstat_internal.h"
#include "utils/rel.h"
#include "utils/timestamp.h"
/*
* Structures in which backends store per-table info that's waiting to be
* sent to the collector.
*
* NOTE: once allocated, TabStatusArray structures are never moved or deleted
* for the life of the backend. Also, we zero out the t_id fields of the
* contained PgStat_TableStatus structs whenever they are not actively in use.
* This allows relcache pgstat_info pointers to be treated as long-lived data,
* avoiding repeated searches in pgstat_relation_init() when a relation is
* repeatedly opened during a transaction.
*/
#define TABSTAT_QUANTUM 100 /* we alloc this many at a time */
typedef struct TabStatusArray
{
struct TabStatusArray *tsa_next; /* link to next array, if any */
int tsa_used; /* # entries currently used */
PgStat_TableStatus tsa_entries[TABSTAT_QUANTUM]; /* per-table data */
} TabStatusArray;
static TabStatusArray *pgStatTabList = NULL;
/*
* pgStatTabHash entry: map from relation OID to PgStat_TableStatus pointer
*/
typedef struct TabStatHashEntry
{
Oid t_id;
PgStat_TableStatus *tsa_entry;
} TabStatHashEntry;
/* Record that's written to 2PC state file when pgstat state is persisted */
typedef struct TwoPhasePgStatRecord
{
PgStat_Counter tuples_inserted; /* tuples inserted in xact */
PgStat_Counter tuples_updated; /* tuples updated in xact */
PgStat_Counter tuples_deleted; /* tuples deleted in xact */
/* tuples i/u/d prior to truncate/drop */
PgStat_Counter inserted_pre_truncdrop;
PgStat_Counter updated_pre_truncdrop;
PgStat_Counter deleted_pre_truncdrop;
Oid t_id; /* table's OID */
bool t_shared; /* is it a shared catalog? */
bool t_truncdropped; /* was the relation truncated/dropped? */
} TwoPhasePgStatRecord;
static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared);
static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg, TimestampTz now);
static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level);
static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info);
static void pgstat_truncdrop_save_counters(PgStat_TableXactStatus *trans, bool is_drop);
static void pgstat_truncdrop_restore_counters(PgStat_TableXactStatus *trans);
/*
* Indicates if backend has some relation stats that it hasn't yet
* sent to the collector.
*/
bool have_relation_stats;
/*
* Hash table for O(1) t_id -> tsa_entry lookup
*/
static HTAB *pgStatTabHash = NULL;
/* ----------
* pgstat_relation_init() -
*
* Initialize a relcache entry to count access statistics.
* Called whenever a relation is opened.
*
* We assume that a relcache entry's pgstat_info field is zeroed by
* relcache.c when the relcache entry is made; thereafter it is long-lived
* data. We can avoid repeated searches of the TabStatus arrays when the
* same relation is touched repeatedly within a transaction.
* ----------
*/
void
pgstat_relation_init(Relation rel)
{
Oid rel_id = rel->rd_id;
char relkind = rel->rd_rel->relkind;
/*
* We only count stats for relations with storage and partitioned tables
*/
if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
{
rel->pgstat_info = NULL;
return;
}
if (pgStatSock == PGINVALID_SOCKET || !pgstat_track_counts)
{
/* We're not counting at all */
rel->pgstat_info = NULL;
return;
}
/*
* If we already set up this relation in the current transaction, nothing
* to do.
*/
if (rel->pgstat_info != NULL &&
rel->pgstat_info->t_id == rel_id)
return;
/* Else find or make the PgStat_TableStatus entry, and update link */
rel->pgstat_info = get_tabstat_entry(rel_id, rel->rd_rel->relisshared);
}
/* ----------
* pgstat_drop_relation() -
*
* Tell the collector that we just dropped a relation.
* (If the message gets lost, we will still clean the dead entry eventually
* via future invocations of pgstat_vacuum_stat().)
*
* Currently not used for lack of any good place to call it; we rely
* entirely on pgstat_vacuum_stat() to clean out stats for dead rels.
* ----------
*/
#ifdef NOT_USED
void
pgstat_drop_relation(Oid relid)
{
PgStat_MsgTabpurge msg;
int len;
if (pgStatSock == PGINVALID_SOCKET)
return;
msg.m_tableid[0] = relid;
msg.m_nentries = 1;
len = offsetof(PgStat_MsgTabpurge, m_tableid[0]) + sizeof(Oid);
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TABPURGE);
msg.m_databaseid = MyDatabaseId;
pgstat_send(&msg, len);
}
#endif /* NOT_USED */
/* ----------
* pgstat_report_autovac() -
*
* Called from autovacuum.c to report startup of an autovacuum process.
* We are called before InitPostgres is done, so can't rely on MyDatabaseId;
* the db OID must be passed in, instead.
* ----------
*/
void
pgstat_report_autovac(Oid dboid)
{
PgStat_MsgAutovacStart msg;
if (pgStatSock == PGINVALID_SOCKET)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_AUTOVAC_START);
msg.m_databaseid = dboid;
msg.m_start_time = GetCurrentTimestamp();
pgstat_send(&msg, sizeof(msg));
}
/* ---------
* pgstat_report_vacuum() -
*
* Tell the collector about the table we just vacuumed.
* ---------
*/
void
pgstat_report_vacuum(Oid tableoid, bool shared,
PgStat_Counter livetuples, PgStat_Counter deadtuples)
{
PgStat_MsgVacuum msg;
if (pgStatSock == PGINVALID_SOCKET || !pgstat_track_counts)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_VACUUM);
msg.m_databaseid = shared ? InvalidOid : MyDatabaseId;
msg.m_tableoid = tableoid;
msg.m_autovacuum = IsAutoVacuumWorkerProcess();
msg.m_vacuumtime = GetCurrentTimestamp();
msg.m_live_tuples = livetuples;
msg.m_dead_tuples = deadtuples;
pgstat_send(&msg, sizeof(msg));
}
/* --------
* pgstat_report_analyze() -
*
* Tell the collector about the table we just analyzed.
*
* Caller must provide new live- and dead-tuples estimates, as well as a
* flag indicating whether to reset the changes_since_analyze counter.
* --------
*/
void
pgstat_report_analyze(Relation rel,
PgStat_Counter livetuples, PgStat_Counter deadtuples,
bool resetcounter)
{
PgStat_MsgAnalyze msg;
if (pgStatSock == PGINVALID_SOCKET || !pgstat_track_counts)
return;
/*
* Unlike VACUUM, ANALYZE might be running inside a transaction that has
* already inserted and/or deleted rows in the target table. ANALYZE will
* have counted such rows as live or dead respectively. Because we will
* report our counts of such rows at transaction end, we should subtract
* off these counts from what we send to the collector now, else they'll
* be double-counted after commit. (This approach also ensures that the
* collector ends up with the right numbers if we abort instead of
* committing.)
*
* Waste no time on partitioned tables, though.
*/
if (pgstat_relation_should_count(rel) &&
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
{
PgStat_TableXactStatus *trans;
for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
{
livetuples -= trans->tuples_inserted - trans->tuples_deleted;
deadtuples -= trans->tuples_updated + trans->tuples_deleted;
}
/* count stuff inserted by already-aborted subxacts, too */
deadtuples -= rel->pgstat_info->t_counts.t_delta_dead_tuples;
/* Since ANALYZE's counts are estimates, we could have underflowed */
livetuples = Max(livetuples, 0);
deadtuples = Max(deadtuples, 0);
}
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_ANALYZE);
msg.m_databaseid = rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId;
msg.m_tableoid = RelationGetRelid(rel);
msg.m_autovacuum = IsAutoVacuumWorkerProcess();
msg.m_resetcounter = resetcounter;
msg.m_analyzetime = GetCurrentTimestamp();
msg.m_live_tuples = livetuples;
msg.m_dead_tuples = deadtuples;
pgstat_send(&msg, sizeof(msg));
}
/*
* pgstat_count_heap_insert - count a tuple insertion of n tuples
*/
void
pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
{
if (pgstat_relation_should_count(rel))
{
PgStat_TableStatus *pgstat_info = rel->pgstat_info;
ensure_tabstat_xact_level(pgstat_info);
pgstat_info->trans->tuples_inserted += n;
}
}
/*
* pgstat_count_heap_update - count a tuple update
*/
void
pgstat_count_heap_update(Relation rel, bool hot)
{
if (pgstat_relation_should_count(rel))
{
PgStat_TableStatus *pgstat_info = rel->pgstat_info;
ensure_tabstat_xact_level(pgstat_info);
pgstat_info->trans->tuples_updated++;
/* t_tuples_hot_updated is nontransactional, so just advance it */
if (hot)
pgstat_info->t_counts.t_tuples_hot_updated++;
}
}
/*
* pgstat_count_heap_delete - count a tuple deletion
*/
void
pgstat_count_heap_delete(Relation rel)
{
if (pgstat_relation_should_count(rel))
{
PgStat_TableStatus *pgstat_info = rel->pgstat_info;
ensure_tabstat_xact_level(pgstat_info);
pgstat_info->trans->tuples_deleted++;
}
}
/*
* pgstat_count_truncate - update tuple counters due to truncate
*/
void
pgstat_count_truncate(Relation rel)
{
if (pgstat_relation_should_count(rel))
{
PgStat_TableStatus *pgstat_info = rel->pgstat_info;
ensure_tabstat_xact_level(pgstat_info);
pgstat_truncdrop_save_counters(pgstat_info->trans, false);
pgstat_info->trans->tuples_inserted = 0;
pgstat_info->trans->tuples_updated = 0;
pgstat_info->trans->tuples_deleted = 0;
}
}
/*
* pgstat_update_heap_dead_tuples - update dead-tuples count
*
* The semantics of this are that we are reporting the nontransactional
* recovery of "delta" dead tuples; so t_delta_dead_tuples decreases
* rather than increasing, and the change goes straight into the per-table
* counter, not into transactional state.
*/
void
pgstat_update_heap_dead_tuples(Relation rel, int delta)
{
if (pgstat_relation_should_count(rel))
{
PgStat_TableStatus *pgstat_info = rel->pgstat_info;
pgstat_info->t_counts.t_delta_dead_tuples -= delta;
}
}
/*
* find_tabstat_entry - find any existing PgStat_TableStatus entry for rel
*
* If no entry, return NULL, don't create a new one
*
* Note: if we got an error in the most recent execution of pgstat_report_stat,
* it's possible that an entry exists but there's no hashtable entry for it.
* That's okay, we'll treat this case as "doesn't exist".
*/
PgStat_TableStatus *
find_tabstat_entry(Oid rel_id)
{
TabStatHashEntry *hash_entry;
/* If hashtable doesn't exist, there are no entries at all */
if (!pgStatTabHash)
return NULL;
hash_entry = hash_search(pgStatTabHash, &rel_id, HASH_FIND, NULL);
if (!hash_entry)
return NULL;
/* Note that this step could also return NULL, but that's correct */
return hash_entry->tsa_entry;
}
/*
* Perform relation stats specific end-of-transaction work. Helper for
* AtEOXact_PgStat.
*
* Transfer transactional insert/update counts into the base tabstat entries.
* We don't bother to free any of the transactional state, since it's all in
* TopTransactionContext and will go away anyway.
*/
void
AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
{
PgStat_TableXactStatus *trans;
for (trans = xact_state->first; trans != NULL; trans = trans->next)
{
PgStat_TableStatus *tabstat;
Assert(trans->nest_level == 1);
Assert(trans->upper == NULL);
tabstat = trans->parent;
Assert(tabstat->trans == trans);
/* restore pre-truncate/drop stats (if any) in case of aborted xact */
if (!isCommit)
pgstat_truncdrop_restore_counters(trans);
/* count attempted actions regardless of commit/abort */
tabstat->t_counts.t_tuples_inserted += trans->tuples_inserted;
tabstat->t_counts.t_tuples_updated += trans->tuples_updated;
tabstat->t_counts.t_tuples_deleted += trans->tuples_deleted;
if (isCommit)
{
tabstat->t_counts.t_truncdropped = trans->truncdropped;
if (trans->truncdropped)
{
/* forget live/dead stats seen by backend thus far */
tabstat->t_counts.t_delta_live_tuples = 0;
tabstat->t_counts.t_delta_dead_tuples = 0;
}
/* insert adds a live tuple, delete removes one */
tabstat->t_counts.t_delta_live_tuples +=
trans->tuples_inserted - trans->tuples_deleted;
/* update and delete each create a dead tuple */
tabstat->t_counts.t_delta_dead_tuples +=
trans->tuples_updated + trans->tuples_deleted;
/* insert, update, delete each count as one change event */
tabstat->t_counts.t_changed_tuples +=
trans->tuples_inserted + trans->tuples_updated +
trans->tuples_deleted;
}
else
{
/* inserted tuples are dead, deleted tuples are unaffected */
tabstat->t_counts.t_delta_dead_tuples +=
trans->tuples_inserted + trans->tuples_updated;
/* an aborted xact generates no changed_tuple events */
}
tabstat->trans = NULL;
}
}
/*
* Perform relation stats specific end-of-sub-transaction work. Helper for
* AtEOSubXact_PgStat.
*
* Transfer transactional insert/update counts into the next higher
* subtransaction state.
*/
void
AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
{
PgStat_TableXactStatus *trans;
PgStat_TableXactStatus *next_trans;
for (trans = xact_state->first; trans != NULL; trans = next_trans)
{
PgStat_TableStatus *tabstat;
next_trans = trans->next;
Assert(trans->nest_level == nestDepth);
tabstat = trans->parent;
Assert(tabstat->trans == trans);
if (isCommit)
{
if (trans->upper && trans->upper->nest_level == nestDepth - 1)
{
if (trans->truncdropped)
{
/* propagate the truncate/drop status one level up */
pgstat_truncdrop_save_counters(trans->upper, false);
/* replace upper xact stats with ours */
trans->upper->tuples_inserted = trans->tuples_inserted;
trans->upper->tuples_updated = trans->tuples_updated;
trans->upper->tuples_deleted = trans->tuples_deleted;
}
else
{
trans->upper->tuples_inserted += trans->tuples_inserted;
trans->upper->tuples_updated += trans->tuples_updated;
trans->upper->tuples_deleted += trans->tuples_deleted;
}
tabstat->trans = trans->upper;
pfree(trans);
}
else
{
/*
* When there isn't an immediate parent state, we can just
* reuse the record instead of going through a palloc/pfree
* pushup (this works since it's all in TopTransactionContext
* anyway). We have to re-link it into the parent level,
* though, and that might mean pushing a new entry into the
* pgStatXactStack.
*/
PgStat_SubXactStatus *upper_xact_state;
upper_xact_state = pgstat_xact_stack_level_get(nestDepth - 1);
trans->next = upper_xact_state->first;
upper_xact_state->first = trans;
trans->nest_level = nestDepth - 1;
}
}
else
{
/*
* On abort, update top-level tabstat counts, then forget the
* subtransaction
*/
/* first restore values obliterated by truncate/drop */
pgstat_truncdrop_restore_counters(trans);
/* count attempted actions regardless of commit/abort */
tabstat->t_counts.t_tuples_inserted += trans->tuples_inserted;
tabstat->t_counts.t_tuples_updated += trans->tuples_updated;
tabstat->t_counts.t_tuples_deleted += trans->tuples_deleted;
/* inserted tuples are dead, deleted tuples are unaffected */
tabstat->t_counts.t_delta_dead_tuples +=
trans->tuples_inserted + trans->tuples_updated;
tabstat->trans = trans->upper;
pfree(trans);
}
}
}
/*
* Generate 2PC records for all the pending transaction-dependent relation
* stats.
*/
void
AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
{
PgStat_TableXactStatus *trans;
for (trans = xact_state->first; trans != NULL; trans = trans->next)
{
PgStat_TableStatus *tabstat;
TwoPhasePgStatRecord record;
Assert(trans->nest_level == 1);
Assert(trans->upper == NULL);
tabstat = trans->parent;
Assert(tabstat->trans == trans);
record.tuples_inserted = trans->tuples_inserted;
record.tuples_updated = trans->tuples_updated;
record.tuples_deleted = trans->tuples_deleted;
record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop;
record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
record.t_id = tabstat->t_id;
record.t_shared = tabstat->t_shared;
record.t_truncdropped = trans->truncdropped;
RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0,
&record, sizeof(TwoPhasePgStatRecord));
}
}
/*
* All we need do here is unlink the transaction stats state from the
* nontransactional state. The nontransactional action counts will be
* reported to the stats collector immediately, while the effects on
* live and dead tuple counts are preserved in the 2PC state file.
*
* Note: AtEOXact_PgStat_Relations is not called during PREPARE.
*/
void
PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
{
PgStat_TableXactStatus *trans;
for (trans = xact_state->first; trans != NULL; trans = trans->next)
{
PgStat_TableStatus *tabstat;
tabstat = trans->parent;
tabstat->trans = NULL;
}
}
/*
* 2PC processing routine for COMMIT PREPARED case.
*
* Load the saved counts into our local pgstats state.
*/
void
pgstat_twophase_postcommit(TransactionId xid, uint16 info,
void *recdata, uint32 len)
{
TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
PgStat_TableStatus *pgstat_info;
/* Find or create a tabstat entry for the rel */
pgstat_info = get_tabstat_entry(rec->t_id, rec->t_shared);
/* Same math as in AtEOXact_PgStat, commit case */
pgstat_info->t_counts.t_tuples_inserted += rec->tuples_inserted;
pgstat_info->t_counts.t_tuples_updated += rec->tuples_updated;
pgstat_info->t_counts.t_tuples_deleted += rec->tuples_deleted;
pgstat_info->t_counts.t_truncdropped = rec->t_truncdropped;
if (rec->t_truncdropped)
{
/* forget live/dead stats seen by backend thus far */
pgstat_info->t_counts.t_delta_live_tuples = 0;
pgstat_info->t_counts.t_delta_dead_tuples = 0;
}
pgstat_info->t_counts.t_delta_live_tuples +=
rec->tuples_inserted - rec->tuples_deleted;
pgstat_info->t_counts.t_delta_dead_tuples +=
rec->tuples_updated + rec->tuples_deleted;
pgstat_info->t_counts.t_changed_tuples +=
rec->tuples_inserted + rec->tuples_updated +
rec->tuples_deleted;
}
/*
* 2PC processing routine for ROLLBACK PREPARED case.
*
* Load the saved counts into our local pgstats state, but treat them
* as aborted.
*/
void
pgstat_twophase_postabort(TransactionId xid, uint16 info,
void *recdata, uint32 len)
{
TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
PgStat_TableStatus *pgstat_info;
/* Find or create a tabstat entry for the rel */
pgstat_info = get_tabstat_entry(rec->t_id, rec->t_shared);
/* Same math as in AtEOXact_PgStat, abort case */
if (rec->t_truncdropped)
{
rec->tuples_inserted = rec->inserted_pre_truncdrop;
rec->tuples_updated = rec->updated_pre_truncdrop;
rec->tuples_deleted = rec->deleted_pre_truncdrop;
}
pgstat_info->t_counts.t_tuples_inserted += rec->tuples_inserted;
pgstat_info->t_counts.t_tuples_updated += rec->tuples_updated;
pgstat_info->t_counts.t_tuples_deleted += rec->tuples_deleted;
pgstat_info->t_counts.t_delta_dead_tuples +=
rec->tuples_inserted + rec->tuples_updated;
}
/*
* Subroutine for pgstat_report_stat: Send relation statistics
*/
void
pgstat_send_tabstats(TimestampTz now, bool disconnect)
{
/* we assume this inits to all zeroes: */
static const PgStat_TableCounts all_zeroes;
PgStat_MsgTabstat regular_msg;
PgStat_MsgTabstat shared_msg;
TabStatusArray *tsa;
int i;
/*
* Destroy pgStatTabHash before we start invalidating PgStat_TableEntry
* entries it points to. (Should we fail partway through the loop below,
* it's okay to have removed the hashtable already --- the only
* consequence is we'd get multiple entries for the same table in the
* pgStatTabList, and that's safe.)
*/
if (pgStatTabHash)
hash_destroy(pgStatTabHash);
pgStatTabHash = NULL;
/*
* Scan through the TabStatusArray struct(s) to find tables that actually
* have counts, and build messages to send. We have to separate shared
* relations from regular ones because the databaseid field in the message
* header has to depend on that.
*/
regular_msg.m_databaseid = MyDatabaseId;
shared_msg.m_databaseid = InvalidOid;
regular_msg.m_nentries = 0;
shared_msg.m_nentries = 0;
for (tsa = pgStatTabList; tsa != NULL; tsa = tsa->tsa_next)
{
for (i = 0; i < tsa->tsa_used; i++)
{
PgStat_TableStatus *entry = &tsa->tsa_entries[i];
PgStat_MsgTabstat *this_msg;
PgStat_TableEntry *this_ent;
/* Shouldn't have any pending transaction-dependent counts */
Assert(entry->trans == NULL);
/*
* Ignore entries that didn't accumulate any actual counts, such
* as indexes that were opened by the planner but not used.
*/
if (memcmp(&entry->t_counts, &all_zeroes,
sizeof(PgStat_TableCounts)) == 0)
continue;
/*
* OK, insert data into the appropriate message, and send if full.
*/
this_msg = entry->t_shared ? &shared_msg : &regular_msg;
this_ent = &this_msg->m_entry[this_msg->m_nentries];
this_ent->t_id = entry->t_id;
memcpy(&this_ent->t_counts, &entry->t_counts,
sizeof(PgStat_TableCounts));
if (++this_msg->m_nentries >= PGSTAT_NUM_TABENTRIES)
{
pgstat_send_tabstat(this_msg, now);
this_msg->m_nentries = 0;
}
}
/* zero out PgStat_TableStatus structs after use */
MemSet(tsa->tsa_entries, 0,
tsa->tsa_used * sizeof(PgStat_TableStatus));
tsa->tsa_used = 0;
}
/*
* Send partial messages. Make sure that any pending xact commit/abort
* and connection stats get counted, even if there are no table stats to
* send.
*/
if (regular_msg.m_nentries > 0 ||
pgStatXactCommit > 0 || pgStatXactRollback > 0 || disconnect)
pgstat_send_tabstat(&regular_msg, now);
if (shared_msg.m_nentries > 0)
pgstat_send_tabstat(&shared_msg, now);
have_relation_stats = false;
}
/*
* Subroutine for pgstat_send_tabstats: finish and send one tabstat message
*/
static void
pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg, TimestampTz now)
{
int n;
int len;
/* It's unlikely we'd get here with no socket, but maybe not impossible */
if (pgStatSock == PGINVALID_SOCKET)
return;
/*
* Report and reset accumulated xact commit/rollback and I/O timings
* whenever we send a normal tabstat message
*/
pgstat_update_dbstats(tsmsg, now);
n = tsmsg->m_nentries;
len = offsetof(PgStat_MsgTabstat, m_entry[0]) +
n * sizeof(PgStat_TableEntry);
pgstat_setheader(&tsmsg->m_hdr, PGSTAT_MTYPE_TABSTAT);
pgstat_send(tsmsg, len);
}
/*
* get_tabstat_entry - find or create a PgStat_TableStatus entry for rel
*/
static PgStat_TableStatus *
get_tabstat_entry(Oid rel_id, bool isshared)
{
TabStatHashEntry *hash_entry;
PgStat_TableStatus *entry;
TabStatusArray *tsa;
bool found;
pgstat_assert_is_up();
have_relation_stats = true;
/*
* Create hash table if we don't have it already.
*/
if (pgStatTabHash == NULL)
{
HASHCTL ctl;
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(TabStatHashEntry);
pgStatTabHash = hash_create("pgstat TabStatusArray lookup hash table",
TABSTAT_QUANTUM,
&ctl,
HASH_ELEM | HASH_BLOBS);
}
/*
* Find an entry or create a new one.
*/
hash_entry = hash_search(pgStatTabHash, &rel_id, HASH_ENTER, &found);
if (!found)
{
/* initialize new entry with null pointer */
hash_entry->tsa_entry = NULL;
}
/*
* If entry is already valid, we're done.
*/
if (hash_entry->tsa_entry)
return hash_entry->tsa_entry;
/*
* Locate the first pgStatTabList entry with free space, making a new list
* entry if needed. Note that we could get an OOM failure here, but if so
* we have left the hashtable and the list in a consistent state.
*/
if (pgStatTabList == NULL)
{
/* Set up first pgStatTabList entry */
pgStatTabList = (TabStatusArray *)
MemoryContextAllocZero(TopMemoryContext,
sizeof(TabStatusArray));
}
tsa = pgStatTabList;
while (tsa->tsa_used >= TABSTAT_QUANTUM)
{
if (tsa->tsa_next == NULL)
tsa->tsa_next = (TabStatusArray *)
MemoryContextAllocZero(TopMemoryContext,
sizeof(TabStatusArray));
tsa = tsa->tsa_next;
}
/*
* Allocate a PgStat_TableStatus entry within this list entry. We assume
* the entry was already zeroed, either at creation or after last use.
*/
entry = &tsa->tsa_entries[tsa->tsa_used++];
entry->t_id = rel_id;
entry->t_shared = isshared;
/*
* Now we can fill the entry in pgStatTabHash.
*/
hash_entry->tsa_entry = entry;
return entry;
}
/*
* add_tabstat_xact_level - add a new (sub)transaction state record
*/
static void
add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
{
PgStat_SubXactStatus *xact_state;
PgStat_TableXactStatus *trans;
/*
* If this is the first rel to be modified at the current nest level, we
* first have to push a transaction stack entry.
*/
xact_state = pgstat_xact_stack_level_get(nest_level);
/* Now make a per-table stack entry */
trans = (PgStat_TableXactStatus *)
MemoryContextAllocZero(TopTransactionContext,
sizeof(PgStat_TableXactStatus));
trans->nest_level = nest_level;
trans->upper = pgstat_info->trans;
trans->parent = pgstat_info;
trans->next = xact_state->first;
xact_state->first = trans;
pgstat_info->trans = trans;
}
/*
* Add a new (sub)transaction record if needed.
*/
static void
ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info)
{
int nest_level = GetCurrentTransactionNestLevel();
if (pgstat_info->trans == NULL ||
pgstat_info->trans->nest_level != nest_level)
add_tabstat_xact_level(pgstat_info, nest_level);
}
/*
* pgstat_truncdrop_save_counters
*
* Whenever a table is truncated/dropped, we save its i/u/d counters so that
* they can be cleared, and if the (sub)xact that executed the truncate/drop
* later aborts, the counters can be restored to the saved (pre-truncate/drop)
* values.
*
* Note that for truncate we do this on the first truncate in any particular
* subxact level only.
*/
static void
pgstat_truncdrop_save_counters(PgStat_TableXactStatus *trans, bool is_drop)
{
if (!trans->truncdropped || is_drop)
{
trans->inserted_pre_truncdrop = trans->tuples_inserted;
trans->updated_pre_truncdrop = trans->tuples_updated;
trans->deleted_pre_truncdrop = trans->tuples_deleted;
trans->truncdropped = true;
}
}
/*
* pgstat_truncdrop_restore_counters - restore counters when a truncate aborts
*/
static void
pgstat_truncdrop_restore_counters(PgStat_TableXactStatus *trans)
{
if (trans->truncdropped)
{
trans->tuples_inserted = trans->inserted_pre_truncdrop;
trans->tuples_updated = trans->updated_pre_truncdrop;
trans->tuples_deleted = trans->deleted_pre_truncdrop;
}
}

View File

@ -0,0 +1,119 @@
/* -------------------------------------------------------------------------
*
* pgstat_replslot.c
* Implementation of replication slot statistics.
*
* This file contains the implementation of replication slot statistics. It is kept
* separate from pgstat.c to enforce the line between the statistics access /
* storage implementation and the details about individual types of
* statistics.
*
* Copyright (c) 2001-2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/utils/activity/pgstat_replslot.c
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "replication/slot.h"
#include "utils/builtins.h" /* for namestrcpy() */
#include "utils/pgstat_internal.h"
/* ----------
* pgstat_reset_replslot_counter() -
*
* Tell the statistics collector to reset a single replication slot
* counter, or all replication slots counters (when name is null).
*
* Permission checking for this function is managed through the normal
* GRANT system.
* ----------
*/
void
pgstat_reset_replslot_counter(const char *name)
{
PgStat_MsgResetreplslotcounter msg;
if (pgStatSock == PGINVALID_SOCKET)
return;
if (name)
{
namestrcpy(&msg.m_slotname, name);
msg.clearall = false;
}
else
msg.clearall = true;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETREPLSLOTCOUNTER);
pgstat_send(&msg, sizeof(msg));
}
/* ----------
* pgstat_report_replslot() -
*
* Tell the collector about replication slot statistics.
* ----------
*/
void
pgstat_report_replslot(const PgStat_StatReplSlotEntry *repSlotStat)
{
PgStat_MsgReplSlot msg;
/*
* Prepare and send the message
*/
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_REPLSLOT);
namestrcpy(&msg.m_slotname, NameStr(repSlotStat->slotname));
msg.m_create = false;
msg.m_drop = false;
msg.m_spill_txns = repSlotStat->spill_txns;
msg.m_spill_count = repSlotStat->spill_count;
msg.m_spill_bytes = repSlotStat->spill_bytes;
msg.m_stream_txns = repSlotStat->stream_txns;
msg.m_stream_count = repSlotStat->stream_count;
msg.m_stream_bytes = repSlotStat->stream_bytes;
msg.m_total_txns = repSlotStat->total_txns;
msg.m_total_bytes = repSlotStat->total_bytes;
pgstat_send(&msg, sizeof(PgStat_MsgReplSlot));
}
/* ----------
* pgstat_report_replslot_create() -
*
* Tell the collector about creating the replication slot.
* ----------
*/
void
pgstat_report_replslot_create(const char *slotname)
{
PgStat_MsgReplSlot msg;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_REPLSLOT);
namestrcpy(&msg.m_slotname, slotname);
msg.m_create = true;
msg.m_drop = false;
pgstat_send(&msg, sizeof(PgStat_MsgReplSlot));
}
/* ----------
* pgstat_report_replslot_drop() -
*
* Tell the collector about dropping the replication slot.
* ----------
*/
void
pgstat_report_replslot_drop(const char *slotname)
{
PgStat_MsgReplSlot msg;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_REPLSLOT);
namestrcpy(&msg.m_slotname, slotname);
msg.m_create = false;
msg.m_drop = true;
pgstat_send(&msg, sizeof(PgStat_MsgReplSlot));
}

View File

@ -0,0 +1,201 @@
/* -------------------------------------------------------------------------
*
* pgstat_slru.c
* Implementation of SLRU statistics.
*
* This file contains the implementation of SLRU statistics. It is kept
* separate from pgstat.c to enforce the line between the statistics access /
* storage implementation and the details about individual types of
* statistics.
*
* Copyright (c) 2001-2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/utils/activity/pgstat_slru.c
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "utils/pgstat_internal.h"
static inline PgStat_MsgSLRU *slru_entry(int slru_idx);
/*
* SLRU statistics counts waiting to be sent to the collector. These are
* stored directly in stats message format so they can be sent without needing
* to copy things around. We assume this variable inits to zeroes. Entries
* are one-to-one with slru_names[].
*/
static PgStat_MsgSLRU SLRUStats[SLRU_NUM_ELEMENTS];
/* ----------
* 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));
}
/*
* SLRU statistics count accumulation functions --- called from slru.c
*/
void
pgstat_count_slru_page_zeroed(int slru_idx)
{
slru_entry(slru_idx)->m_blocks_zeroed += 1;
}
void
pgstat_count_slru_page_hit(int slru_idx)
{
slru_entry(slru_idx)->m_blocks_hit += 1;
}
void
pgstat_count_slru_page_exists(int slru_idx)
{
slru_entry(slru_idx)->m_blocks_exists += 1;
}
void
pgstat_count_slru_page_read(int slru_idx)
{
slru_entry(slru_idx)->m_blocks_read += 1;
}
void
pgstat_count_slru_page_written(int slru_idx)
{
slru_entry(slru_idx)->m_blocks_written += 1;
}
void
pgstat_count_slru_flush(int slru_idx)
{
slru_entry(slru_idx)->m_flush += 1;
}
void
pgstat_count_slru_truncate(int slru_idx)
{
slru_entry(slru_idx)->m_truncate += 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.
*/
const char *
pgstat_slru_name(int slru_idx)
{
if (slru_idx < 0 || slru_idx >= SLRU_NUM_ELEMENTS)
return NULL;
return slru_names[slru_idx];
}
/*
* 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 projects.
*/
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_send_slru() -
*
* Send SLRU statistics to the collector
* ----------
*/
void
pgstat_send_slru(void)
{
/* We assume this initializes to zeroes */
static const PgStat_MsgSLRU all_zeroes;
for (int 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));
}
}
/*
* slru_entry
*
* Returns pointer to entry with counters for given SLRU (based on the name
* stored in SlruCtl as lwlock tranche name).
*/
static inline PgStat_MsgSLRU *
slru_entry(int slru_idx)
{
pgstat_assert_is_up();
/*
* The postmaster should never register any SLRU statistics counts; if it
* did, the counts would be duplicated into child processes via fork().
*/
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert((slru_idx >= 0) && (slru_idx < SLRU_NUM_ELEMENTS));
return &SLRUStats[slru_idx];
}

View File

@ -0,0 +1,78 @@
/* -------------------------------------------------------------------------
*
* pgstat_subscription.c
* Implementation of subscription statistics.
*
* This file contains the implementation of subscription statistics. It is kept
* separate from pgstat.c to enforce the line between the statistics access /
* storage implementation and the details about individual types of
* statistics.
*
* Copyright (c) 2001-2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/utils/activity/pgstat_subscription.c
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "utils/pgstat_internal.h"
/* ----------
* pgstat_reset_subscription_counter() -
*
* Tell the statistics collector to reset a single subscription
* counter, or all subscription counters (when subid is InvalidOid).
*
* Permission checking for this function is managed through the normal
* GRANT system.
* ----------
*/
void
pgstat_reset_subscription_counter(Oid subid)
{
PgStat_MsgResetsubcounter msg;
if (pgStatSock == PGINVALID_SOCKET)
return;
msg.m_subid = subid;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSUBCOUNTER);
pgstat_send(&msg, sizeof(msg));
}
/* ----------
* pgstat_report_subscription_error() -
*
* Tell the collector about the subscription error.
* ----------
*/
void
pgstat_report_subscription_error(Oid subid, bool is_apply_error)
{
PgStat_MsgSubscriptionError msg;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_SUBSCRIPTIONERROR);
msg.m_subid = subid;
msg.m_is_apply_error = is_apply_error;
pgstat_send(&msg, sizeof(PgStat_MsgSubscriptionError));
}
/* ----------
* pgstat_report_subscription_drop() -
*
* Tell the collector about dropping the subscription.
* ----------
*/
void
pgstat_report_subscription_drop(Oid subid)
{
PgStat_MsgSubscriptionDrop msg;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_SUBSCRIPTIONDROP);
msg.m_subid = subid;
pgstat_send(&msg, sizeof(PgStat_MsgSubscriptionDrop));
}

View File

@ -0,0 +1,159 @@
/* -------------------------------------------------------------------------
*
* pgstat_wal.c
* Implementation of WAL statistics.
*
* This file contains the implementation of WAL statistics. It is kept
* separate from pgstat.c to enforce the line between the statistics access /
* storage implementation and the details about individual types of
* statistics.
*
* Copyright (c) 2001-2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/utils/activity/pgstat_wal.c
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "utils/pgstat_internal.h"
#include "executor/instrument.h"
/*
* WAL global statistics counters. Stored directly in a stats message
* structure so they can be sent without needing to copy things around. We
* assume these init to zeroes.
*/
PgStat_MsgWal WalStats;
/*
* WAL usage counters saved from pgWALUsage at the previous call to
* pgstat_send_wal(). This is used to calculate how much WAL usage
* happens between pgstat_send_wal() calls, by subtracting
* the previous counters from the current ones.
*/
static WalUsage prevWalUsage;
/* ----------
* pgstat_send_wal() -
*
* Send WAL statistics to the collector.
*
* If 'force' is not set, WAL stats message is only sent if enough time has
* passed since last one was sent to reach PGSTAT_STAT_INTERVAL.
* ----------
*/
void
pgstat_send_wal(bool force)
{
static TimestampTz sendTime = 0;
/*
* 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.
*
* Check wal_records counter to determine whether any WAL activity has
* happened since last time. Note that other WalUsage counters don't need
* to be checked because they are incremented always together with
* wal_records counter.
*
* m_wal_buffers_full also doesn't need to be checked because it's
* incremented only when at least one WAL record is generated (i.e.,
* wal_records counter is incremented). But for safely, we assert that
* m_wal_buffers_full is always zero when no WAL record is generated
*
* This function can be called by a process like walwriter that normally
* generates no WAL records. To determine whether any WAL activity has
* happened at that process since the last time, the numbers of WAL writes
* and syncs are also checked.
*/
if (pgWalUsage.wal_records == prevWalUsage.wal_records &&
WalStats.m_wal_write == 0 && WalStats.m_wal_sync == 0)
{
Assert(WalStats.m_wal_buffers_full == 0);
return;
}
if (!force)
{
TimestampTz now = GetCurrentTimestamp();
/*
* Don't send a message unless it's been at least PGSTAT_STAT_INTERVAL
* msec since we last sent one to avoid overloading the stats
* collector.
*/
if (!TimestampDifferenceExceeds(sendTime, now, PGSTAT_STAT_INTERVAL))
return;
sendTime = now;
}
/*
* Set the counters related to generated WAL data if the counters were
* updated.
*/
if (pgWalUsage.wal_records != prevWalUsage.wal_records)
{
WalUsage walusage;
/*
* Calculate how much WAL usage counters were increased by subtracting
* the previous counters from the current ones. Fill the results in
* WAL stats message.
*/
MemSet(&walusage, 0, sizeof(WalUsage));
WalUsageAccumDiff(&walusage, &pgWalUsage, &prevWalUsage);
WalStats.m_wal_records = walusage.wal_records;
WalStats.m_wal_fpi = walusage.wal_fpi;
WalStats.m_wal_bytes = walusage.wal_bytes;
/*
* Save the current counters for the subsequent calculation of WAL
* usage.
*/
prevWalUsage = pgWalUsage;
}
/*
* Prepare and send the message
*/
pgstat_setheader(&WalStats.m_hdr, PGSTAT_MTYPE_WAL);
pgstat_send(&WalStats, sizeof(WalStats));
/*
* Clear out the statistics buffer, so it can be re-used.
*/
MemSet(&WalStats, 0, sizeof(WalStats));
}
void
pgstat_wal_initialize(void)
{
/*
* Initialize prevWalUsage with pgWalUsage so that pgstat_send_wal() can
* calculate how much pgWalUsage counters are increased by subtracting
* prevWalUsage from pgWalUsage.
*/
prevWalUsage = pgWalUsage;
}
/*
* To determine whether any WAL activity has occurred since last time, not
* only the number of generated WAL records but also the numbers of WAL
* writes and syncs need to be checked. Because even transaction that
* generates no WAL records can write or sync WAL data when flushing the
* data pages.
*/
bool
pgstat_wal_pending(void)
{
return pgWalUsage.wal_records != prevWalUsage.wal_records ||
WalStats.m_wal_write != 0 ||
WalStats.m_wal_sync != 0;
}

View File

@ -0,0 +1,161 @@
/* ----------
* pgstat_internal.h
*
* Definitions for the PostgreSQL activity statistics facility that should
* only be needed by files implementing statistics support (rather than ones
* reporting / querying stats).
*
* Copyright (c) 2001-2022, PostgreSQL Global Development Group
*
* src/include/utils/pgstat_internal.h
* ----------
*/
#ifndef PGSTAT_INTERNAL_H
#define PGSTAT_INTERNAL_H
#include "pgstat.h"
#define PGSTAT_STAT_INTERVAL 500 /* Minimum time between stats file
* updates; in milliseconds. */
/* ----------
* The initial size hints for the hash tables used in the collector.
* ----------
*/
#define PGSTAT_DB_HASH_SIZE 16
#define PGSTAT_TAB_HASH_SIZE 512
#define PGSTAT_FUNCTION_HASH_SIZE 512
#define PGSTAT_SUBSCRIPTION_HASH_SIZE 32
#define PGSTAT_REPLSLOT_HASH_SIZE 32
/*
* Some stats changes are transactional. To maintain those, a stack of
* PgStat_SubXactStatus entries is maintained, which contain data pertaining
* to the current transaction and its active subtransactions.
*/
typedef struct PgStat_SubXactStatus
{
int nest_level; /* subtransaction nest level */
struct PgStat_SubXactStatus *prev; /* higher-level subxact if any */
/*
* Tuple insertion/deletion counts for an open transaction can't be
* propagated into PgStat_TableStatus counters until we know if it is
* going to commit or abort. Hence, we keep these counts in per-subxact
* structs that live in TopTransactionContext. This data structure is
* designed on the assumption that subxacts won't usually modify very many
* tables.
*/
PgStat_TableXactStatus *first; /* head of list for this subxact */
} PgStat_SubXactStatus;
/*
* List of SLRU names that we keep stats for. There is no central registry of
* SLRUs, so we use this fixed list instead. The "other" entry is used for
* all SLRUs without an explicit entry (e.g. SLRUs in extensions).
*/
static const char *const slru_names[] = {
"CommitTs",
"MultiXactMember",
"MultiXactOffset",
"Notify",
"Serial",
"Subtrans",
"Xact",
"other" /* has to be last */
};
#define SLRU_NUM_ELEMENTS lengthof(slru_names)
/*
* Functions in pgstat.c
*/
extern PgStat_SubXactStatus *pgstat_xact_stack_level_get(int nest_level);
extern void pgstat_setheader(PgStat_MsgHdr *hdr, StatMsgType mtype);
extern void pgstat_send(void *msg, int len);
#ifdef USE_ASSERT_CHECKING
extern void pgstat_assert_is_up(void);
#else
#define pgstat_assert_is_up() ((void)true)
#endif
/*
* Functions in pgstat_database.c
*/
extern void AtEOXact_PgStat_Database(bool isCommit, bool parallel);
extern void pgstat_report_disconnect(Oid dboid);
extern void pgstat_update_dbstats(PgStat_MsgTabstat *tsmsg, TimestampTz now);
/*
* Functions in pgstat_function.c
*/
extern void pgstat_send_funcstats(void);
/*
* Functions in pgstat_relation.c
*/
extern void AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit);
extern void AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
extern void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
extern void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
extern void pgstat_send_tabstats(TimestampTz now, bool disconnect);
/*
* Functions in pgstat_slru.c
*/
extern void pgstat_send_slru(void);
/*
* Functions in pgstat_wal.c
*/
extern void pgstat_wal_initialize(void);
extern bool pgstat_wal_pending(void);
/*
* Variables in pgstat.c
*/
extern pgsocket pgStatSock;
/*
* Variables in pgstat_database.c
*/
extern int pgStatXactCommit;
extern int pgStatXactRollback;
/*
* Variables in pgstat_functions.c
*/
extern bool have_function_stats;
/*
* Variables in pgstat_relation.c
*/
extern bool have_relation_stats;
#endif /* PGSTAT_INTERNAL_H */