| /* ------------------------------------------------------------------------- |
| * |
| * 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-2023, PostgreSQL Global Development Group |
| * |
| * IDENTIFICATION |
| * src/backend/utils/activity/pgstat_database.c |
| * ------------------------------------------------------------------------- |
| */ |
| |
| #include "postgres.h" |
| |
| #include "utils/pgstat_internal.h" |
| #include "utils/timestamp.h" |
| #include "storage/procsignal.h" |
| |
| |
| static bool pgstat_should_report_connstat(void); |
| |
| |
| PgStat_Counter pgStatBlockReadTime = 0; |
| PgStat_Counter pgStatBlockWriteTime = 0; |
| PgStat_Counter pgStatActiveTime = 0; |
| PgStat_Counter pgStatTransactionIdleTime = 0; |
| SessionEndType pgStatSessionEndCause = DISCONNECT_NORMAL; |
| |
| |
| static int pgStatXactCommit = 0; |
| static int pgStatXactRollback = 0; |
| static PgStat_Counter pgLastSessionReportTime = 0; |
| |
| |
| /* |
| * Remove entry for the database being dropped. |
| */ |
| void |
| pgstat_drop_database(Oid databaseid) |
| { |
| pgstat_drop_transactional(PGSTAT_KIND_DATABASE, databaseid, InvalidOid); |
| } |
| |
| /* |
| * 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_EntryRef *entry_ref; |
| PgStatShared_Database *dbentry; |
| |
| /* can't get here in single user mode */ |
| Assert(IsUnderPostmaster); |
| |
| /* |
| * End-of-vacuum is reported instantly. Report the start the same way for |
| * consistency. Vacuum doesn't run frequently and is a long-lasting |
| * operation so it doesn't matter if we get blocked here a little. |
| */ |
| entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, |
| dboid, InvalidOid, false); |
| |
| dbentry = (PgStatShared_Database *) entry_ref->shared_stats; |
| dbentry->stats.last_autovac_time = GetCurrentTimestamp(); |
| |
| pgstat_unlock_entry(entry_ref); |
| } |
| |
| /* |
| * Report a Hot Standby recovery conflict. |
| */ |
| void |
| pgstat_report_recovery_conflict(int reason) |
| { |
| PgStat_StatDBEntry *dbentry; |
| |
| Assert(IsUnderPostmaster); |
| if (!pgstat_track_counts) |
| return; |
| |
| dbentry = pgstat_prep_database_pending(MyDatabaseId); |
| |
| switch (reason) |
| { |
| case PROCSIG_RECOVERY_CONFLICT_DATABASE: |
| |
| /* |
| * Since we drop the information about the database as soon as it |
| * replicates, there is no point in counting these conflicts. |
| */ |
| break; |
| case PROCSIG_RECOVERY_CONFLICT_TABLESPACE: |
| dbentry->conflict_tablespace++; |
| break; |
| case PROCSIG_RECOVERY_CONFLICT_LOCK: |
| dbentry->conflict_lock++; |
| break; |
| case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT: |
| dbentry->conflict_snapshot++; |
| break; |
| case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN: |
| dbentry->conflict_bufferpin++; |
| break; |
| case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT: |
| dbentry->conflict_logicalslot++; |
| break; |
| case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK: |
| dbentry->conflict_startup_deadlock++; |
| break; |
| } |
| } |
| |
| /* |
| * Report a detected deadlock. |
| */ |
| void |
| pgstat_report_deadlock(void) |
| { |
| PgStat_StatDBEntry *dbent; |
| |
| if (!pgstat_track_counts) |
| return; |
| |
| dbent = pgstat_prep_database_pending(MyDatabaseId); |
| dbent->deadlocks++; |
| } |
| |
| /* |
| * Report one or more checksum failures. |
| */ |
| void |
| pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount) |
| { |
| PgStat_EntryRef *entry_ref; |
| PgStatShared_Database *sharedent; |
| |
| if (!pgstat_track_counts) |
| return; |
| |
| /* |
| * Update the shared stats directly - checksum failures should never be |
| * common enough for that to be a problem. |
| */ |
| entry_ref = |
| pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, dboid, InvalidOid, false); |
| |
| sharedent = (PgStatShared_Database *) entry_ref->shared_stats; |
| sharedent->stats.checksum_failures += failurecount; |
| sharedent->stats.last_checksum_failure = GetCurrentTimestamp(); |
| |
| pgstat_unlock_entry(entry_ref); |
| } |
| |
| /* |
| * Report one checksum failure in the current database. |
| */ |
| void |
| pgstat_report_checksum_failure(void) |
| { |
| pgstat_report_checksum_failures_in_db(MyDatabaseId, 1); |
| } |
| |
| /* |
| * Report creation of temporary file. |
| */ |
| void |
| pgstat_report_tempfile(size_t filesize) |
| { |
| PgStat_StatDBEntry *dbent; |
| |
| if (!pgstat_track_counts) |
| return; |
| |
| dbent = pgstat_prep_database_pending(MyDatabaseId); |
| dbent->temp_bytes += filesize; |
| dbent->temp_files++; |
| } |
| |
| /* |
| * Notify stats system of a new connection. |
| */ |
| void |
| pgstat_report_connect(Oid dboid) |
| { |
| PgStat_StatDBEntry *dbentry; |
| |
| if (!pgstat_should_report_connstat()) |
| return; |
| |
| pgLastSessionReportTime = MyStartTimestamp; |
| |
| dbentry = pgstat_prep_database_pending(MyDatabaseId); |
| dbentry->sessions++; |
| } |
| |
| /* |
| * Notify the stats system of a disconnect. |
| */ |
| void |
| pgstat_report_disconnect(Oid dboid) |
| { |
| PgStat_StatDBEntry *dbentry; |
| |
| if (!pgstat_should_report_connstat()) |
| return; |
| |
| dbentry = pgstat_prep_database_pending(MyDatabaseId); |
| |
| switch (pgStatSessionEndCause) |
| { |
| case DISCONNECT_NOT_YET: |
| case DISCONNECT_NORMAL: |
| /* we don't collect these */ |
| break; |
| case DISCONNECT_CLIENT_EOF: |
| dbentry->sessions_abandoned++; |
| break; |
| case DISCONNECT_FATAL: |
| dbentry->sessions_fatal++; |
| break; |
| case DISCONNECT_KILLED: |
| dbentry->sessions_killed++; |
| break; |
| } |
| } |
| |
| /* |
| * Support function for the SQL-callable pgstat* functions. Returns |
| * the collected statistics for one database or NULL. NULL doesn't mean |
| * that the database doesn't exist, just that there are no statistics, so the |
| * caller is better off to report ZERO instead. |
| */ |
| PgStat_StatDBEntry * |
| pgstat_fetch_stat_dbentry(Oid dboid) |
| { |
| return (PgStat_StatDBEntry *) |
| pgstat_fetch_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid); |
| } |
| |
| 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_report_stat(): Handle xact commit/rollback and I/O |
| * timings. |
| */ |
| void |
| pgstat_update_dbstats(TimestampTz ts) |
| { |
| PgStat_StatDBEntry *dbentry; |
| |
| /* |
| * If not connected to a database yet, don't attribute time to "shared |
| * state" (InvalidOid is used to track stats for shared relations, etc.). |
| */ |
| if (!OidIsValid(MyDatabaseId)) |
| return; |
| |
| dbentry = pgstat_prep_database_pending(MyDatabaseId); |
| |
| /* |
| * Accumulate xact commit/rollback and I/O timings to stats entry of the |
| * current database. |
| */ |
| dbentry->xact_commit += pgStatXactCommit; |
| dbentry->xact_rollback += pgStatXactRollback; |
| dbentry->blk_read_time += pgStatBlockReadTime; |
| dbentry->blk_write_time += pgStatBlockWriteTime; |
| |
| if (pgstat_should_report_connstat()) |
| { |
| long secs; |
| int usecs; |
| |
| /* |
| * pgLastSessionReportTime is initialized to MyStartTimestamp by |
| * pgstat_report_connect(). |
| */ |
| TimestampDifference(pgLastSessionReportTime, ts, &secs, &usecs); |
| pgLastSessionReportTime = ts; |
| dbentry->session_time += (PgStat_Counter) secs * 1000000 + usecs; |
| dbentry->active_time += pgStatActiveTime; |
| dbentry->idle_in_transaction_time += pgStatTransactionIdleTime; |
| } |
| |
| pgStatXactCommit = 0; |
| pgStatXactRollback = 0; |
| pgStatBlockReadTime = 0; |
| pgStatBlockWriteTime = 0; |
| pgStatActiveTime = 0; |
| pgStatTransactionIdleTime = 0; |
| } |
| |
| /* |
| * 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; |
| } |
| |
| /* |
| * Find or create a local PgStat_StatDBEntry entry for dboid. |
| */ |
| PgStat_StatDBEntry * |
| pgstat_prep_database_pending(Oid dboid) |
| { |
| PgStat_EntryRef *entry_ref; |
| |
| /* |
| * This should not report stats on database objects before having |
| * connected to a database. |
| */ |
| Assert(!OidIsValid(dboid) || OidIsValid(MyDatabaseId)); |
| |
| entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid, |
| NULL); |
| |
| return entry_ref->pending; |
| } |
| |
| /* |
| * Reset the database's reset timestamp, without resetting the contents of the |
| * database stats. |
| */ |
| void |
| pgstat_reset_database_timestamp(Oid dboid, TimestampTz ts) |
| { |
| PgStat_EntryRef *dbref; |
| PgStatShared_Database *dbentry; |
| |
| dbref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, MyDatabaseId, InvalidOid, |
| false); |
| |
| dbentry = (PgStatShared_Database *) dbref->shared_stats; |
| dbentry->stats.stat_reset_timestamp = ts; |
| |
| pgstat_unlock_entry(dbref); |
| } |
| |
| /* |
| * Flush out pending stats for the entry |
| * |
| * If nowait is true and the lock could not be immediately acquired, returns |
| * false without flushing the entry. Otherwise returns true. |
| */ |
| bool |
| pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) |
| { |
| PgStatShared_Database *sharedent; |
| PgStat_StatDBEntry *pendingent; |
| |
| pendingent = (PgStat_StatDBEntry *) entry_ref->pending; |
| sharedent = (PgStatShared_Database *) entry_ref->shared_stats; |
| |
| if (!pgstat_lock_entry(entry_ref, nowait)) |
| return false; |
| |
| #define PGSTAT_ACCUM_DBCOUNT(item) \ |
| (sharedent)->stats.item += (pendingent)->item |
| |
| PGSTAT_ACCUM_DBCOUNT(xact_commit); |
| PGSTAT_ACCUM_DBCOUNT(xact_rollback); |
| PGSTAT_ACCUM_DBCOUNT(blocks_fetched); |
| PGSTAT_ACCUM_DBCOUNT(blocks_hit); |
| |
| PGSTAT_ACCUM_DBCOUNT(tuples_returned); |
| PGSTAT_ACCUM_DBCOUNT(tuples_fetched); |
| PGSTAT_ACCUM_DBCOUNT(tuples_inserted); |
| PGSTAT_ACCUM_DBCOUNT(tuples_updated); |
| PGSTAT_ACCUM_DBCOUNT(tuples_deleted); |
| |
| /* last_autovac_time is reported immediately */ |
| Assert(pendingent->last_autovac_time == 0); |
| |
| PGSTAT_ACCUM_DBCOUNT(conflict_tablespace); |
| PGSTAT_ACCUM_DBCOUNT(conflict_lock); |
| PGSTAT_ACCUM_DBCOUNT(conflict_snapshot); |
| PGSTAT_ACCUM_DBCOUNT(conflict_logicalslot); |
| PGSTAT_ACCUM_DBCOUNT(conflict_bufferpin); |
| PGSTAT_ACCUM_DBCOUNT(conflict_startup_deadlock); |
| |
| PGSTAT_ACCUM_DBCOUNT(temp_bytes); |
| PGSTAT_ACCUM_DBCOUNT(temp_files); |
| PGSTAT_ACCUM_DBCOUNT(deadlocks); |
| |
| /* checksum failures are reported immediately */ |
| Assert(pendingent->checksum_failures == 0); |
| Assert(pendingent->last_checksum_failure == 0); |
| |
| PGSTAT_ACCUM_DBCOUNT(blk_read_time); |
| PGSTAT_ACCUM_DBCOUNT(blk_write_time); |
| |
| PGSTAT_ACCUM_DBCOUNT(sessions); |
| PGSTAT_ACCUM_DBCOUNT(session_time); |
| PGSTAT_ACCUM_DBCOUNT(active_time); |
| PGSTAT_ACCUM_DBCOUNT(idle_in_transaction_time); |
| PGSTAT_ACCUM_DBCOUNT(sessions_abandoned); |
| PGSTAT_ACCUM_DBCOUNT(sessions_fatal); |
| PGSTAT_ACCUM_DBCOUNT(sessions_killed); |
| #undef PGSTAT_ACCUM_DBCOUNT |
| |
| pgstat_unlock_entry(entry_ref); |
| |
| memset(pendingent, 0, sizeof(*pendingent)); |
| |
| return true; |
| } |
| |
| void |
| pgstat_database_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts) |
| { |
| ((PgStatShared_Database *) header)->stats.stat_reset_timestamp = ts; |
| } |