summaryrefslogtreecommitdiff
path: root/src/backend/postmaster/pgstat.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/postmaster/pgstat.c')
-rw-r--r--src/backend/postmaster/pgstat.c2715
1 files changed, 0 insertions, 2715 deletions
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
deleted file mode 100644
index b578305b4c5..00000000000
--- a/src/backend/postmaster/pgstat.c
+++ /dev/null
@@ -1,2715 +0,0 @@
-/* ----------
- * pgstat.c
- *
- * All the statistics collector stuff hacked up in one big, ugly file.
- *
- * TODO: - Separate collector, postmaster and backend stuff
- * into different files.
- *
- * - Add some automatic call for pgstat vacuuming.
- *
- * - Add a pgstat config column to pg_database, so this
- * entire thing can be enabled/disabled on a per db base.
- * Not to be done before 7.2 - requires catalog change and
- * thus an initdb and we might want to provide this as a
- * patch for 7.1.
- *
- * Copyright (c) 2001, PostgreSQL Global Development Group
- *
- * $Header: /cvsroot/pgsql/src/backend/postmaster/pgstat.c,v 1.21 2002/05/20 23:51:43 tgl Exp $
- * ----------
- */
-#include "postgres.h"
-
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/param.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <errno.h>
-#include <signal.h>
-
-#include "access/xact.h"
-#include "access/heapam.h"
-#include "catalog/catname.h"
-#include "catalog/pg_shadow.h"
-#include "catalog/pg_database.h"
-#include "libpq/pqsignal.h"
-#ifdef MULTIBYTE
-#include "mb/pg_wchar.h"
-#endif
-#include "miscadmin.h"
-#include "utils/memutils.h"
-#include "storage/backendid.h"
-#include "storage/ipc.h"
-#include "utils/rel.h"
-#include "utils/hsearch.h"
-#include "utils/ps_status.h"
-#include "utils/syscache.h"
-
-#include "pgstat.h"
-
-
-/* ----------
- * GUC parameters
- * ----------
- */
-bool pgstat_collect_startcollector = true;
-bool pgstat_collect_resetonpmstart = true;
-bool pgstat_collect_querystring = false;
-bool pgstat_collect_tuplelevel = false;
-bool pgstat_collect_blocklevel = false;
-
-/* ----------
- * Local data
- * ----------
- */
-static int pgStatSock = -1;
-static int pgStatPipe[2];
-static struct sockaddr_in pgStatAddr;
-static int pgStatPmPipe[2] = {-1, -1};
-
-static int pgStatRunning = 0;
-static int pgStatPid;
-
-static long pgStatNumMessages = 0;
-
-static bool pgStatRunningInCollector = FALSE;
-static int pgStatTabstatAlloc = 0;
-static int pgStatTabstatUsed = 0;
-static PgStat_MsgTabstat **pgStatTabstatMessages = NULL;
-static int pgStatXactCommit = 0;
-static int pgStatXactRollback = 0;
-
-
-static TransactionId pgStatDBHashXact = InvalidTransactionId;
-static HTAB *pgStatDBHash = NULL;
-static HTAB *pgStatBeDead = NULL;
-static PgStat_StatBeEntry *pgStatBeTable = NULL;
-static int pgStatNumBackends = 0;
-
-static char pgStat_tmpfname[MAXPGPATH];
-static char pgStat_fname[MAXPGPATH];
-
-
-/* ----------
- * Local function forward declarations
- * ----------
- */
-static void pgstat_main(void);
-static void pgstat_recvbuffer(void);
-static void pgstat_die(SIGNAL_ARGS);
-
-static int pgstat_add_backend(PgStat_MsgHdr *msg);
-static void pgstat_sub_backend(int procpid);
-static void pgstat_drop_database(Oid databaseid);
-static void pgstat_write_statsfile(void);
-static void pgstat_read_statsfile(HTAB **dbhash, Oid onlydb,
- PgStat_StatBeEntry **betab,
- int *numbackends);
-
-static void pgstat_setheader(PgStat_MsgHdr *hdr, int mtype);
-static void pgstat_send(void *msg, int len);
-
-static void pgstat_recv_bestart(PgStat_MsgBestart *msg, int len);
-static void pgstat_recv_beterm(PgStat_MsgBeterm *msg, int len);
-static void pgstat_recv_activity(PgStat_MsgActivity *msg, int len);
-static void pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len);
-static void pgstat_recv_tabpurge(PgStat_MsgTabpurge *msg, int len);
-static void pgstat_recv_dropdb(PgStat_MsgDropdb *msg, int len);
-static void pgstat_recv_resetcounter(PgStat_MsgResetcounter *msg, int len);
-
-
-/* ------------------------------------------------------------
- * Public functions called from postmaster follow
- * ------------------------------------------------------------
- */
-
-
-/* ----------
- * pgstat_init() -
- *
- * Called from postmaster at startup. Create the resources required
- * by the statistics collector process.
- *
- * NOTE: failure exit from this routine causes the postmaster to abort.
- * This is unfriendly and should not be done except in dire straits.
- * Better to let the postmaster start with stats collection disabled.
- * ----------
- */
-int
-pgstat_init(void)
-{
- int alen;
-
- /*
- * Force start of collector daemon if something to collect
- */
- if (pgstat_collect_querystring || pgstat_collect_tuplelevel ||
- pgstat_collect_blocklevel)
- pgstat_collect_startcollector = true;
-
- /*
- * Initialize the filenames for the status reports.
- */
- snprintf(pgStat_tmpfname, MAXPGPATH,
- PGSTAT_STAT_TMPFILE, DataDir, getpid());
- snprintf(pgStat_fname, MAXPGPATH,
- PGSTAT_STAT_FILENAME, DataDir);
-
- /*
- * If we don't have to start a collector or should reset the collected
- * statistics on postmaster start, simply remove the file.
- */
- if (!pgstat_collect_startcollector || pgstat_collect_resetonpmstart)
- unlink(pgStat_fname);
-
- /*
- * Nothing else required if collector will not get started
- */
- if (!pgstat_collect_startcollector)
- return 0;
-
- /*
- * Create the UDP socket for sending and receiving statistic messages
- */
- if ((pgStatSock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
- {
- elog(LOG, "PGSTAT: socket() failed: %m");
- goto startup_failed;
- }
-
- /*
- * Bind it to a kernel assigned port on localhost and get the assigned
- * port via getsockname().
- */
- pgStatAddr.sin_family = AF_INET;
- pgStatAddr.sin_port = htons(0);
- inet_aton("127.0.0.1", &(pgStatAddr.sin_addr));
- alen = sizeof(pgStatAddr);
- if (bind(pgStatSock, (struct sockaddr *) & pgStatAddr, alen) < 0)
- {
- elog(LOG, "PGSTAT: bind(127.0.0.1) failed: %m");
- goto startup_failed;
- }
- if (getsockname(pgStatSock, (struct sockaddr *) & pgStatAddr, &alen) < 0)
- {
- elog(LOG, "PGSTAT: getsockname() failed: %m");
- goto startup_failed;
- }
-
- /*
- * Connect the socket to its own address. This saves a few cycles by
- * not having to respecify the target address on every send. This also
- * provides a kernel-level check that only packets from this same
- * address will be received.
- */
- if (connect(pgStatSock, (struct sockaddr *) & pgStatAddr, alen) < 0)
- {
- elog(LOG, "PGSTAT: connect() failed: %m");
- goto startup_failed;
- }
-
- /*
- * Set the socket to non-blocking IO. This ensures that if the
- * collector falls behind (despite the buffering process), statistics
- * messages will be discarded; backends won't block waiting to send
- * messages to the collector.
- */
- if (fcntl(pgStatSock, F_SETFL, O_NONBLOCK) < 0)
- {
- elog(LOG, "PGSTAT: fcntl() failed: %m");
- goto startup_failed;
- }
-
- /*
- * Create the pipe that controls the statistics collector shutdown
- */
- if (pipe(pgStatPmPipe) < 0)
- {
- elog(LOG, "PGSTAT: pipe() failed: %m");
- goto startup_failed;
- }
-
- return 0;
-
-startup_failed:
- if (pgStatSock >= 0)
- close(pgStatSock);
- pgStatSock = -1;
-
- /* Adjust GUC variables to suppress useless activity */
- pgstat_collect_querystring = false;
- pgstat_collect_tuplelevel = false;
- pgstat_collect_blocklevel = false;
-
- return 0;
-}
-
-
-/* ----------
- * pgstat_start() -
- *
- * Called from postmaster at startup or after an existing collector
- * died. Fire up a fresh statistics collector.
- *
- * NOTE: failure exit from this routine causes the postmaster to abort.
- * ----------
- */
-int
-pgstat_start(void)
-{
- /*
- * Do nothing if no collector needed
- */
- if (!pgstat_collect_startcollector)
- return 0;
-
- /*
- * Check that the socket is there, else pgstat_init failed
- */
- if (pgStatSock < 0)
- {
- elog(LOG, "PGSTAT: statistics collector startup skipped");
- return 0;
- }
-
- /*
- * Then fork off the collector. Remember its PID for pgstat_ispgstat.
- */
-
- fflush(stdout);
- fflush(stderr);
-
-#ifdef __BEOS__
- /* Specific beos actions before backend startup */
- beos_before_backend_startup();
-#endif
-
- switch ((pgStatPid = (int) fork()))
- {
- case -1:
-#ifdef __BEOS__
- /* Specific beos actions */
- beos_backend_startup_failed();
-#endif
- elog(LOG, "PGSTAT: fork() failed: %m");
- pgStatRunning = 0;
- return 0;
-
- case 0:
- break;
-
- default:
- pgStatRunning = 1;
- return 0;
- }
-
- /* in postmaster child ... */
-
-#ifdef __BEOS__
- /* Specific beos actions after backend startup */
- beos_backend_startup();
-#endif
-
- IsUnderPostmaster = true; /* we are a postmaster subprocess now */
-
- /* Lose the postmaster's on-exit routines */
- on_exit_reset();
-
- /* Close the postmaster's sockets, except for pgstat link */
- ClosePostmasterPorts(false);
-
- pgstat_main();
-
- exit(0);
-}
-
-
-/* ----------
- * pgstat_ispgstat() -
- *
- * Called from postmaster to check if a terminated child process
- * was the statistics collector.
- * ----------
- */
-int
-pgstat_ispgstat(int pid)
-{
- if (pgStatRunning == 0)
- return 0;
-
- if (pgStatPid != pid)
- return 0;
-
- return 1;
-}
-
-
-/* ----------
- * pgstat_close_sockets() -
- *
- * Called when postmaster forks a non-pgstat child process, to close off
- * file descriptors that should not be held open in child processes.
- * ----------
- */
-void
-pgstat_close_sockets(void)
-{
- if (pgStatPmPipe[0] >= 0)
- close(pgStatPmPipe[0]);
- pgStatPmPipe[0] = -1;
- if (pgStatPmPipe[1] >= 0)
- close(pgStatPmPipe[1]);
- pgStatPmPipe[1] = -1;
-}
-
-
-/* ----------
- * pgstat_beterm() -
- *
- * Called from postmaster to tell collector a backend terminated.
- * ----------
- */
-void
-pgstat_beterm(int pid)
-{
- PgStat_MsgBeterm msg;
-
- if (pgStatSock < 0)
- return;
-
- msg.m_hdr.m_type = PGSTAT_MTYPE_BETERM;
- msg.m_hdr.m_backendid = 0;
- msg.m_hdr.m_procpid = pid;
- msg.m_hdr.m_databaseid = 0;
-
- pgstat_send(&msg, sizeof(msg));
-}
-
-
-/* ------------------------------------------------------------
- * Public functions used by backends follow
- *------------------------------------------------------------
- */
-
-
-/* ----------
- * pgstat_bestart() -
- *
- * Tell the collector that this new backend is soon ready to process
- * queries. Called from tcop/postgres.c before entering the mainloop.
- * ----------
- */
-void
-pgstat_bestart(void)
-{
- PgStat_MsgBestart msg;
-
- if (pgStatSock < 0)
- return;
-
- pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_BESTART);
- pgstat_send(&msg, sizeof(msg));
-}
-
-
-/* ----------
- * pgstat_report_activity() -
- *
- * Called in tcop/postgres.c to tell the collector what the backend
- * is actually doing (usually "<IDLE>" or the start of the query to
- * be executed).
- * ----------
- */
-void
-pgstat_report_activity(char *what)
-{
- PgStat_MsgActivity msg;
- int len;
-
- if (!pgstat_collect_querystring || pgStatSock < 0)
- return;
-
- len = strlen(what);
-#ifdef MULTIBYTE
- len = pg_mbcliplen((const unsigned char *)what, len, PGSTAT_ACTIVITY_SIZE - 1);
-#else
- if (len >= PGSTAT_ACTIVITY_SIZE)
- len = PGSTAT_ACTIVITY_SIZE - 1;
-#endif
-
- memcpy(msg.m_what, what, len);
- msg.m_what[len] = '\0';
- len += offsetof(PgStat_MsgActivity, m_what) +1;
-
- pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_ACTIVITY);
- pgstat_send(&msg, len);
-}
-
-
-/* ----------
- * pgstat_report_tabstat() -
- *
- * Called from tcop/postgres.c to send the so far collected
- * per table access statistics to the collector.
- * ----------
- */
-void
-pgstat_report_tabstat(void)
-{
- int i;
- int n;
- int len;
-
- if (!pgstat_collect_querystring && !pgstat_collect_tuplelevel &&
- !pgstat_collect_blocklevel)
- return;
-
- if (pgStatSock < 0)
- return;
-
- /*
- * For each message buffer used during the last query set the header
- * fields and send it out.
- */
- for (i = 0; i < pgStatTabstatUsed; i++)
- {
- n = pgStatTabstatMessages[i]->m_nentries;
- len = offsetof(PgStat_MsgTabstat, m_entry[0]) +
- n * sizeof(PgStat_TableEntry);
-
- pgStatTabstatMessages[i]->m_xact_commit = pgStatXactCommit;
- pgStatTabstatMessages[i]->m_xact_rollback = pgStatXactRollback;
- pgStatXactCommit = 0;
- pgStatXactRollback = 0;
-
- pgstat_setheader(&pgStatTabstatMessages[i]->m_hdr,
- PGSTAT_MTYPE_TABSTAT);
- pgstat_send(pgStatTabstatMessages[i], len);
- }
-
- pgStatTabstatUsed = 0;
-}
-
-
-/* ----------
- * pgstat_vacuum_tabstat() -
- *
- * Will tell the collector about objects he can get rid of.
- * ----------
- */
-int
-pgstat_vacuum_tabstat(void)
-{
- Relation dbrel;
- HeapScanDesc dbscan;
- HeapTuple dbtup;
- Oid *dbidlist;
- int dbidalloc;
- int dbidused;
- HASH_SEQ_STATUS hstat;
- PgStat_StatDBEntry *dbentry;
- PgStat_StatTabEntry *tabentry;
- HeapTuple reltup;
- int nobjects = 0;
- PgStat_MsgTabpurge msg;
- int len;
- int i;
-
- if (pgStatSock < 0)
- return 0;
-
- /*
- * We don't vacuum inside of transaction blocks, because a possible
- * later rollback might reactivate objects we didn't find because of
- * earlier destruction in the xact.
- */
- if (IsTransactionBlock())
- return 0;
-
- /*
- * If not done for this transaction, read the statistics collector
- * stats file into some hash tables.
- */
- if (!TransactionIdEquals(pgStatDBHashXact, GetCurrentTransactionId()))
- {
- pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId,
- &pgStatBeTable, &pgStatNumBackends);
- pgStatDBHashXact = GetCurrentTransactionId();
- }
-
- /*
- * Lookup our own database entry
- */
- dbentry = (PgStat_StatDBEntry *) hash_search(pgStatDBHash,
- (void *) &MyDatabaseId,
- HASH_FIND, NULL);
- if (dbentry == NULL)
- return -1;
-
- if (dbentry->tables == NULL)
- return 0;
-
- /*
- * Initialize our messages table counter to zero
- */
- msg.m_nentries = 0;
-
- /*
- * Check for all tables if they still exist.
- */
- hash_seq_init(&hstat, dbentry->tables);
- while ((tabentry = (PgStat_StatTabEntry *) hash_seq_search(&hstat)) != NULL)
- {
- /*
- * Check if this relation is still alive by looking up it's
- * pg_class tuple in the system catalog cache.
- */
- reltup = SearchSysCache(RELOID,
- ObjectIdGetDatum(tabentry->tableid),
- 0, 0, 0);
- if (HeapTupleIsValid(reltup))
- {
- ReleaseSysCache(reltup);
- continue;
- }
-
- /*
- * Add this tables Oid to the message
- */
- msg.m_tableid[msg.m_nentries++] = tabentry->tableid;
- nobjects++;
-
- /*
- * If the message is full, send it out and reinitialize ot zero
- */
- if (msg.m_nentries >= PGSTAT_NUM_TABPURGE)
- {
- len = offsetof(PgStat_MsgTabpurge, m_tableid[0])
- + msg.m_nentries * sizeof(Oid);
-
- pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TABPURGE);
- pgstat_send(&msg, len);
-
- msg.m_nentries = 0;
- }
- }
-
- /*
- * Send the rest
- */
- if (msg.m_nentries > 0)
- {
- len = offsetof(PgStat_MsgTabpurge, m_tableid[0])
- + msg.m_nentries * sizeof(Oid);
-
- pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TABPURGE);
- pgstat_send(&msg, len);
- }
-
- /*
- * Read pg_database and remember the Oid's of all existing databases
- */
- dbidalloc = 256;
- dbidused = 0;
- dbidlist = (Oid *) palloc(sizeof(Oid) * dbidalloc);
-
- dbrel = heap_openr(DatabaseRelationName, AccessShareLock);
- dbscan = heap_beginscan(dbrel, SnapshotNow, 0, NULL);
- while ((dbtup = heap_getnext(dbscan, ForwardScanDirection)) != NULL)
- {
- if (dbidused >= dbidalloc)
- {
- dbidalloc *= 2;
- dbidlist = (Oid *) repalloc((char *) dbidlist,
- sizeof(Oid) * dbidalloc);
- }
- dbidlist[dbidused++] = dbtup->t_data->t_oid;
- }
- heap_endscan(dbscan);
- heap_close(dbrel, AccessShareLock);
-
- /*
- * Search the database hash table for dead databases and tell the
- * collector to drop them as well.
- */
- hash_seq_init(&hstat, pgStatDBHash);
- while ((dbentry = (PgStat_StatDBEntry *) hash_seq_search(&hstat)) != NULL)
- {
- Oid dbid = dbentry->databaseid;
-
- for (i = 0; i < dbidused; i++)
- {
- if (dbidlist[i] == dbid)
- {
- dbid = InvalidOid;
- break;
- }
- }
-
- if (dbid != InvalidOid)
- {
- nobjects++;
- pgstat_drop_database(dbid);
- }
- }
-
- /*
- * Free the dbid list.
- */
- pfree((char *) dbidlist);
-
- /*
- * Tell the caller how many removeable objects we found
- */
- return nobjects;
-}
-
-
-/* ----------
- * pgstat_drop_database() -
- *
- * Tell the collector that we just dropped a database.
- * This is the only message that shouldn't get lost in space. Otherwise
- * the collector will keep the statistics for the dead DB until his
- * stats file got removed while the postmaster is down.
- * ----------
- */
-static void
-pgstat_drop_database(Oid databaseid)
-{
- PgStat_MsgDropdb msg;
-
- if (pgStatSock < 0)
- return;
-
- msg.m_databaseid = databaseid;
-
- pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DROPDB);
- pgstat_send(&msg, sizeof(msg));
-}
-
-
-/* ----------
- * pgstat_reset_counters() -
- *
- * Tell the statistics collector to reset counters for our database.
- * ----------
- */
-void
-pgstat_reset_counters(void)
-{
- PgStat_MsgResetcounter msg;
-
- if (pgStatSock < 0)
- return;
-
- if (!superuser())
- elog(ERROR, "Only database superusers can reset statistic counters");
-
- pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETCOUNTER);
- pgstat_send(&msg, sizeof(msg));
-}
-
-
-/* ----------
- * pgstat_ping() -
- *
- * Send some junk data to the collector to increase traffic.
- * ----------
- */
-void
-pgstat_ping(void)
-{
- PgStat_MsgDummy msg;
-
- if (pgStatSock < 0)
- return;
-
- pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DUMMY);
- pgstat_send(&msg, sizeof(msg));
-}
-
-
-/* ----------
- * pgstat_initstats() -
- *
- * Called from various places usually dealing with initialization
- * of Relation or Scan structures. The data placed into these
- * structures from here tell where later to count for buffer reads,
- * scans and tuples fetched.
- * ----------
- */
-void
-pgstat_initstats(PgStat_Info *stats, Relation rel)
-{
- PgStat_TableEntry *useent = NULL;
- Oid rel_id = rel->rd_id;
- int mb;
- int i;
-
- /*
- * Initialize data not to count at all.
- */
- stats->tabentry = NULL;
- stats->no_stats = FALSE;
- stats->heap_scan_counted = FALSE;
- stats->index_scan_counted = FALSE;
-
- if (pgStatSock < 0)
- {
- stats->no_stats = TRUE;
- return;
- }
-
- /*
- * On the first of all calls initialize the message buffers.
- */
- if (pgStatTabstatAlloc == 0)
- {
- pgStatTabstatAlloc = 4;
- pgStatTabstatMessages = (PgStat_MsgTabstat **)
- malloc(sizeof(PgStat_MsgTabstat *) * pgStatTabstatAlloc);
- if (pgStatTabstatMessages == NULL)
- {
- elog(LOG, "PGSTATBE: malloc() failed");
- return;
- }
- for (i = 0; i < pgStatTabstatAlloc; i++)
- {
- pgStatTabstatMessages[i] = (PgStat_MsgTabstat *)
- malloc(sizeof(PgStat_MsgTabstat));
- if (pgStatTabstatMessages[i] == NULL)
- {
- elog(LOG, "PGSTATBE: malloc() failed");
- return;
- }
- }
- }
-
- /*
- * Lookup the so far used table slots for this relation.
- */
- for (mb = 0; mb < pgStatTabstatUsed; mb++)
- {
- for (i = 0; i < pgStatTabstatMessages[mb]->m_nentries; i++)
- {
- if (pgStatTabstatMessages[mb]->m_entry[i].t_id == rel_id)
- {
- stats->tabentry = (void *) &(pgStatTabstatMessages[mb]->m_entry[i]);
- return;
- }
- }
-
- if (pgStatTabstatMessages[mb]->m_nentries >= PGSTAT_NUM_TABENTRIES)
- continue;
-
- /*
- * Not found, but found a message buffer with an empty slot
- * instead. Fine, let's use this one.
- */
- i = pgStatTabstatMessages[mb]->m_nentries++;
- useent = &pgStatTabstatMessages[mb]->m_entry[i];
- memset(useent, 0, sizeof(PgStat_TableEntry));
- useent->t_id = rel_id;
- stats->tabentry = (void *) useent;
- return;
- }
-
- /*
- * If we ran out of message buffers, we just allocate more.
- */
- if (pgStatTabstatUsed >= pgStatTabstatAlloc)
- {
- pgStatTabstatAlloc += 4;
- pgStatTabstatMessages = (PgStat_MsgTabstat **)
- realloc(pgStatTabstatMessages,
- sizeof(PgStat_MsgTabstat *) * pgStatTabstatAlloc);
- if (pgStatTabstatMessages == NULL)
- {
- pgStatTabstatAlloc -= 4;
- elog(LOG, "PGSTATBE: malloc() failed");
- return;
- }
- for (i = pgStatTabstatUsed; i < pgStatTabstatAlloc; i++)
- {
- pgStatTabstatMessages[i] = (PgStat_MsgTabstat *)
- malloc(sizeof(PgStat_MsgTabstat));
- if (pgStatTabstatMessages[i] == NULL)
- {
- pgStatTabstatAlloc -= 4;
- elog(LOG, "PGSTATBE: malloc() failed");
- return;
- }
- }
- }
-
- /*
- * Use the first entry of the next message buffer.
- */
- mb = pgStatTabstatUsed++;
- pgStatTabstatMessages[mb]->m_nentries = 1;
- useent = &pgStatTabstatMessages[mb]->m_entry[0];
- memset(useent, 0, sizeof(PgStat_TableEntry));
- useent->t_id = rel_id;
- stats->tabentry = (void *) useent;
- return;
-}
-
-
-/* ----------
- * pgstat_count_xact_commit() -
- *
- * Called from access/transam/xact.c to count transaction commits.
- * ----------
- */
-void
-pgstat_count_xact_commit(void)
-{
- if (!pgstat_collect_querystring && !pgstat_collect_tuplelevel &&
- !pgstat_collect_blocklevel)
- return;
-
- pgStatXactCommit++;
-
- /*
- * If there was no relation activity yet, just make one existing
- * message buffer used without slots, causing the next report to tell
- * new xact-counters.
- */
- if (pgStatTabstatAlloc > 0)
- {
- if (pgStatTabstatUsed == 0)
- {
- pgStatTabstatUsed++;
- pgStatTabstatMessages[0]->m_nentries = 0;
- }
- }
-}
-
-
-/* ----------
- * pgstat_count_xact_rollback() -
- *
- * Called from access/transam/xact.c to count transaction rollbacks.
- * ----------
- */
-void
-pgstat_count_xact_rollback(void)
-{
- if (!pgstat_collect_querystring && !pgstat_collect_tuplelevel &&
- !pgstat_collect_blocklevel)
- return;
-
- pgStatXactRollback++;
-
- /*
- * If there was no relation activity yet, just make one existing
- * message buffer used without slots, causing the next report to tell
- * new xact-counters.
- */
- if (pgStatTabstatAlloc > 0)
- {
- if (pgStatTabstatUsed == 0)
- {
- pgStatTabstatUsed++;
- pgStatTabstatMessages[0]->m_nentries = 0;
- }
- }
-}
-
-
-/* ----------
- * pgstat_fetch_stat_dbentry() -
- *
- * 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, it is just not yet known by the
- * collector, so the caller is better off to report ZERO instead.
- * ----------
- */
-PgStat_StatDBEntry *
-pgstat_fetch_stat_dbentry(Oid dbid)
-{
- PgStat_StatDBEntry *dbentry;
-
- /*
- * If not done for this transaction, read the statistics collector
- * stats file into some hash tables. Be careful with the
- * read_statsfile() call below!
- */
- if (!TransactionIdEquals(pgStatDBHashXact, GetCurrentTransactionId()))
- {
- pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId,
- &pgStatBeTable, &pgStatNumBackends);
- pgStatDBHashXact = GetCurrentTransactionId();
- }
-
- /*
- * Lookup the requested database
- */
- dbentry = (PgStat_StatDBEntry *) hash_search(pgStatDBHash,
- (void *) &dbid,
- HASH_FIND, NULL);
- if (dbentry == NULL)
- return NULL;
-
- return dbentry;
-}
-
-
-/* ----------
- * pgstat_fetch_stat_tabentry() -
- *
- * Support function for the SQL-callable pgstat* functions. Returns
- * the collected statistics for one table or NULL. NULL doesn't mean
- * that the table doesn't exist, it is just not yet known by the
- * collector, so the caller is better off to report ZERO instead.
- * ----------
- */
-PgStat_StatTabEntry *
-pgstat_fetch_stat_tabentry(Oid relid)
-{
- PgStat_StatDBEntry *dbentry;
- PgStat_StatTabEntry *tabentry;
-
- /*
- * If not done for this transaction, read the statistics collector
- * stats file into some hash tables. Be careful with the
- * read_statsfile() call below!
- */
- if (!TransactionIdEquals(pgStatDBHashXact, GetCurrentTransactionId()))
- {
- pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId,
- &pgStatBeTable, &pgStatNumBackends);
- pgStatDBHashXact = GetCurrentTransactionId();
- }
-
- /*
- * Lookup our database.
- */
- dbentry = (PgStat_StatDBEntry *) hash_search(pgStatDBHash,
- (void *) &MyDatabaseId,
- HASH_FIND, NULL);
- if (dbentry == NULL)
- return NULL;
-
- /*
- * Now inside the DB's table hash table lookup the requested one.
- */
- if (dbentry->tables == NULL)
- return NULL;
- tabentry = (PgStat_StatTabEntry *) hash_search(dbentry->tables,
- (void *) &relid,
- HASH_FIND, NULL);
- if (tabentry == NULL)
- return NULL;
-
- return tabentry;
-}
-
-
-/* ----------
- * pgstat_fetch_stat_beentry() -
- *
- * Support function for the SQL-callable pgstat* functions. Returns
- * the actual activity slot of one active backend. The caller is
- * responsible for a check if the actual user is permitted to see
- * that info (especially the querystring).
- * ----------
- */
-PgStat_StatBeEntry *
-pgstat_fetch_stat_beentry(int beid)
-{
- if (!TransactionIdEquals(pgStatDBHashXact, GetCurrentTransactionId()))
- {
- pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId,
- &pgStatBeTable, &pgStatNumBackends);
- pgStatDBHashXact = GetCurrentTransactionId();
- }
-
- if (beid < 1 || beid > pgStatNumBackends)
- return NULL;
-
- return &pgStatBeTable[beid - 1];
-}
-
-
-/* ----------
- * pgstat_fetch_stat_numbackends() -
- *
- * Support function for the SQL-callable pgstat* functions. Returns
- * the maximum current backend id.
- * ----------
- */
-int
-pgstat_fetch_stat_numbackends(void)
-{
- if (!TransactionIdEquals(pgStatDBHashXact, GetCurrentTransactionId()))
- {
- pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId,
- &pgStatBeTable, &pgStatNumBackends);
- pgStatDBHashXact = GetCurrentTransactionId();
- }
-
- return pgStatNumBackends;
-}
-
-
-
-/* ------------------------------------------------------------
- * Local support functions follow
- * ------------------------------------------------------------
- */
-
-
-/* ----------
- * pgstat_setheader() -
- *
- * Set common header fields in a statistics message
- * ----------
- */
-static void
-pgstat_setheader(PgStat_MsgHdr *hdr, int mtype)
-{
- hdr->m_type = mtype;
- hdr->m_backendid = MyBackendId;
- hdr->m_procpid = MyProcPid;
- hdr->m_databaseid = MyDatabaseId;
- hdr->m_userid = GetSessionUserId();
-}
-
-
-/* ----------
- * pgstat_send() -
- *
- * Send out one statistics message to the collector
- * ----------
- */
-static void
-pgstat_send(void *msg, int len)
-{
- if (pgStatSock < 0)
- return;
-
- ((PgStat_MsgHdr *) msg)->m_size = len;
-
- send(pgStatSock, msg, len, 0);
- /* We deliberately ignore any error from send() */
-}
-
-
-/* ------------------------------------------------------------
- * Local functions implementing the statistics collector itself follow
- *------------------------------------------------------------
- */
-
-
-/* ----------
- * pgstat_main() -
- *
- * Start up the statistics collector itself. This is the body of the
- * postmaster child process.
- * ----------
- */
-static void
-pgstat_main(void)
-{
- PgStat_Msg msg;
- fd_set rfds;
- int readPipe;
- int pmPipe = pgStatPmPipe[0];
- int maxfd;
- int nready;
- int len = 0;
- struct timeval timeout;
- struct timeval next_statwrite;
- bool need_statwrite;
- HASHCTL hash_ctl;
-
- /*
- * Close the writing end of the postmaster pipe, so we'll see it
- * closing when the postmaster terminates and can terminate as well.
- */
- close(pgStatPmPipe[1]);
- pgStatPmPipe[1] = -1;
-
- /*
- * Ignore all signals usually bound to some action in the postmaster,
- * except for SIGCHLD --- see pgstat_recvbuffer.
- */
- pqsignal(SIGHUP, SIG_IGN);
- pqsignal(SIGINT, SIG_IGN);
- pqsignal(SIGTERM, SIG_IGN);
- pqsignal(SIGQUIT, SIG_IGN);
- pqsignal(SIGALRM, SIG_IGN);
- pqsignal(SIGPIPE, SIG_IGN);
- pqsignal(SIGUSR1, SIG_IGN);
- pqsignal(SIGUSR2, SIG_IGN);
- pqsignal(SIGCHLD, pgstat_die);
- pqsignal(SIGTTIN, SIG_DFL);
- pqsignal(SIGTTOU, SIG_DFL);
- pqsignal(SIGCONT, SIG_DFL);
- pqsignal(SIGWINCH, SIG_DFL);
-
- /*
- * Start a buffering process to read from the socket, so we have a
- * little more time to process incoming messages.
- *
- * NOTE: the process structure is: postmaster is parent of buffer process
- * is parent of collector process. This way, the buffer can detect
- * collector failure via SIGCHLD, whereas otherwise it wouldn't notice
- * collector failure until it tried to write on the pipe. That would
- * mean that after the postmaster started a new collector, we'd have
- * two buffer processes competing to read from the UDP socket --- not
- * good.
- */
- if (pipe(pgStatPipe) < 0)
- {
- elog(LOG, "PGSTAT: pipe() failed: %m");
- exit(1);
- }
-
- switch (fork())
- {
- case -1:
- elog(LOG, "PGSTAT: fork() failed: %m");
- exit(1);
-
- case 0:
- /* child becomes collector process */
- close(pgStatPipe[1]);
- close(pgStatSock);
- break;
-
- default:
- /* parent becomes buffer process */
- close(pgStatPipe[0]);
- pgstat_recvbuffer();
- exit(0);
- }
-
- /*
- * In the child we can have default SIGCHLD handling (in case we want
- * to call system() here...)
- */
- pqsignal(SIGCHLD, SIG_DFL);
-
- /*
- * Identify myself via ps
- */
- init_ps_display("stats collector process", "", "");
- set_ps_display("");
-
- /*
- * Arrange to write the initial status file right away
- */
- gettimeofday(&next_statwrite, NULL);
- need_statwrite = TRUE;
-
- /*
- * Read in an existing statistics stats file or initialize the stats
- * to zero.
- */
- pgStatRunningInCollector = TRUE;
- pgstat_read_statsfile(&pgStatDBHash, InvalidOid, NULL, NULL);
-
- /*
- * Create the dead backend hashtable
- */
- memset(&hash_ctl, 0, sizeof(hash_ctl));
- hash_ctl.keysize = sizeof(int);
- hash_ctl.entrysize = sizeof(PgStat_StatBeDead);
- hash_ctl.hash = tag_hash;
- pgStatBeDead = hash_create("Dead Backends", PGSTAT_BE_HASH_SIZE,
- &hash_ctl, HASH_ELEM | HASH_FUNCTION);
- if (pgStatBeDead == NULL)
- {
- elog(LOG, "PGSTAT: Creation of dead backend hash table failed");
- exit(1);
- }
-
- /*
- * Create the known backends table
- */
- pgStatBeTable = (PgStat_StatBeEntry *) malloc(
- sizeof(PgStat_StatBeEntry) * MaxBackends);
- if (pgStatBeTable == NULL)
- {
- elog(LOG, "PGSTAT: Allocation of backend table failed");
- exit(1);
- }
- memset(pgStatBeTable, 0, sizeof(PgStat_StatBeEntry) * MaxBackends);
-
- readPipe = pgStatPipe[0];
-
- /*
- * Process incoming messages and handle all the reporting stuff until
- * there are no more messages.
- */
- for (;;)
- {
- /*
- * If we need to write the status file again (there have been
- * changes in the statistics since we wrote it last) calculate the
- * timeout until we have to do so.
- */
- if (need_statwrite)
- {
- gettimeofday(&timeout, NULL);
- timeout.tv_usec = next_statwrite.tv_usec - timeout.tv_usec;
- timeout.tv_sec = next_statwrite.tv_sec - timeout.tv_sec;
- if (timeout.tv_usec < 0)
- {
- timeout.tv_sec -= 1;
- timeout.tv_usec += 1000000;
- }
- if (timeout.tv_sec < 0)
- {
- timeout.tv_sec = 0;
- timeout.tv_usec = 0;
- }
- }
-
- /*
- * Setup the descriptor set for select(2)
- */
- FD_ZERO(&rfds);
- FD_SET(readPipe, &rfds);
- FD_SET(pmPipe, &rfds);
-
- if (readPipe > pmPipe)
- maxfd = readPipe;
- else
- maxfd = pmPipe;
-
- /*
- * Now wait for something to do.
- */
- nready = select(maxfd + 1, &rfds, NULL, NULL,
- (need_statwrite) ? &timeout : NULL);
- if (nready < 0)
- {
- if (errno == EINTR)
- continue;
- elog(LOG, "PGSTAT: select() failed: %m");
- exit(1);
- }
-
- /*
- * If there are no descriptors ready, our timeout for writing the
- * stats file happened.
- */
- if (nready == 0)
- {
- pgstat_write_statsfile();
- need_statwrite = FALSE;
-
- continue;
- }
-
- /*
- * Check if there is a new statistics message to collect.
- */
- if (FD_ISSET(readPipe, &rfds))
- {
- /*
- * We may need to issue multiple read calls in case the buffer
- * process didn't write the message in a single write, which
- * is possible since it dumps its buffer bytewise. In any
- * case, we'd need two reads since we don't know the message
- * length initially.
- */
- int nread = 0;
- int targetlen = sizeof(PgStat_MsgHdr); /* initial */
-
- while (nread < targetlen)
- {
- len = read(readPipe,
- ((char *) &msg) + nread,
- targetlen - nread);
- if (len < 0)
- {
- if (errno == EINTR)
- continue;
- elog(LOG, "PGSTAT: read() failed: %m");
- exit(1);
- }
- if (len == 0) /* EOF on the pipe! */
- break;
- nread += len;
- if (nread == sizeof(PgStat_MsgHdr))
- {
- /* we have the header, compute actual msg length */
- targetlen = msg.msg_hdr.m_size;
- if (targetlen < (int) sizeof(PgStat_MsgHdr) ||
- targetlen > (int) sizeof(msg))
- {
- /*
- * Bogus message length implies that we got out of
- * sync with the buffer process somehow. Abort so
- * that we can restart both processes.
- */
- elog(LOG, "PGSTAT: bogus message length");
- exit(1);
- }
- }
- }
-
- /*
- * EOF on the pipe implies that the buffer process exited.
- * Fall out of outer loop.
- */
- if (len == 0)
- break;
-
- /*
- * Distribute the message to the specific function handling
- * it.
- */
- switch (msg.msg_hdr.m_type)
- {
- case PGSTAT_MTYPE_DUMMY:
- break;
-
- case PGSTAT_MTYPE_BESTART:
- pgstat_recv_bestart((PgStat_MsgBestart *) &msg, nread);
- break;
-
- case PGSTAT_MTYPE_BETERM:
- pgstat_recv_beterm((PgStat_MsgBeterm *) &msg, nread);
- break;
-
- case PGSTAT_MTYPE_TABSTAT:
- pgstat_recv_tabstat((PgStat_MsgTabstat *) &msg, nread);
- break;
-
- case PGSTAT_MTYPE_TABPURGE:
- pgstat_recv_tabpurge((PgStat_MsgTabpurge *) &msg, nread);
- break;
-
- case PGSTAT_MTYPE_ACTIVITY:
- pgstat_recv_activity((PgStat_MsgActivity *) &msg, nread);
- break;
-
- case PGSTAT_MTYPE_DROPDB:
- pgstat_recv_dropdb((PgStat_MsgDropdb *) &msg, nread);
- break;
-
- case PGSTAT_MTYPE_RESETCOUNTER:
- pgstat_recv_resetcounter((PgStat_MsgResetcounter *) &msg,
- nread);
- break;
-
- default:
- break;
- }
-
- /*
- * Globally count messages.
- */
- pgStatNumMessages++;
-
- /*
- * If this is the first message after we wrote the stats file
- * the last time, setup the timeout that it'd be written.
- */
- if (!need_statwrite)
- {
- gettimeofday(&next_statwrite, NULL);
- next_statwrite.tv_usec += ((PGSTAT_STAT_INTERVAL) * 1000);
- next_statwrite.tv_sec += (next_statwrite.tv_usec / 1000000);
- next_statwrite.tv_usec %= 1000000;
- need_statwrite = TRUE;
- }
- }
-
- /*
- * Note that we do NOT check for postmaster exit inside the loop;
- * only EOF on the buffer pipe causes us to fall out. This
- * ensures we don't exit prematurely if there are still a few
- * messages in the buffer or pipe at postmaster shutdown.
- */
- }
-
- /*
- * Okay, we saw EOF on the buffer pipe, so there are no more messages
- * to process. If the buffer process quit because of postmaster
- * shutdown, we want to save the final stats to reuse at next startup.
- * But if the buffer process failed, it seems best not to (there may
- * even now be a new collector firing up, and we don't want it to read
- * a partially- rewritten stats file). We can tell whether the
- * postmaster is still alive by checking to see if the postmaster pipe
- * is still open. If it is read-ready (ie, EOF), the postmaster must
- * have quit.
- */
- if (FD_ISSET(pmPipe, &rfds))
- pgstat_write_statsfile();
-}
-
-
-/* ----------
- * pgstat_recvbuffer() -
- *
- * This is the body of the separate buffering process. Its only
- * purpose is to receive messages from the UDP socket as fast as
- * possible and forward them over a pipe into the collector itself.
- * If the collector is slow to absorb messages, they are buffered here.
- * ----------
- */
-static void
-pgstat_recvbuffer(void)
-{
- fd_set rfds;
- fd_set wfds;
- int writePipe = pgStatPipe[1];
- int pmPipe = pgStatPmPipe[0];
- int maxfd;
- int nready;
- int len;
- int xfr;
- int frm;
- PgStat_Msg input_buffer;
- char *msgbuffer;
- int msg_send = 0; /* next send index in buffer */
- int msg_recv = 0; /* next receive index */
- int msg_have = 0; /* number of bytes stored */
- struct sockaddr_in fromaddr;
- int fromlen;
- bool overflow = false;
-
- /*
- * Identify myself via ps
- */
- init_ps_display("stats buffer process", "", "");
- set_ps_display("");
-
- /*
- * We want to die if our child collector process does. There are two
- * ways we might notice that it has died: receive SIGCHLD, or get a
- * write failure on the pipe leading to the child. We can set SIGPIPE
- * to kill us here. Our SIGCHLD handler was already set up before we
- * forked (must do it that way, else it's a race condition).
- */
- pqsignal(SIGPIPE, SIG_DFL);
- PG_SETMASK(&UnBlockSig);
-
- /*
- * Set the write pipe to nonblock mode, so that we cannot block when
- * the collector falls behind.
- */
- if (fcntl(writePipe, F_SETFL, O_NONBLOCK) < 0)
- {
- elog(LOG, "PGSTATBUFF: fcntl() failed: %m");
- exit(1);
- }
-
- /*
- * Allocate the message buffer
- */
- msgbuffer = (char *) malloc(PGSTAT_RECVBUFFERSZ);
- if (msgbuffer == NULL)
- {
- elog(LOG, "PGSTATBUFF: malloc() failed");
- exit(1);
- }
-
- /*
- * Loop forever
- */
- for (;;)
- {
- FD_ZERO(&rfds);
- FD_ZERO(&wfds);
- maxfd = -1;
-
- /*
- * As long as we have buffer space we add the socket to the read
- * descriptor set.
- */
- if (msg_have <= (int) (PGSTAT_RECVBUFFERSZ - sizeof(PgStat_Msg)))
- {
- FD_SET(pgStatSock, &rfds);
- maxfd = pgStatSock;
- overflow = false;
- }
- else
- {
- if (!overflow)
- {
- elog(LOG, "PGSTATBUFF: Warning - receive buffer full");
- overflow = true;
- }
- }
-
- /*
- * If we have messages to write out, we add the pipe to the write
- * descriptor set. Otherwise, we check if the postmaster might
- * have terminated.
- */
- if (msg_have > 0)
- {
- FD_SET(writePipe, &wfds);
- if (writePipe > maxfd)
- maxfd = writePipe;
- }
- else
- {
- FD_SET(pmPipe, &rfds);
- if (pmPipe > maxfd)
- maxfd = pmPipe;
- }
-
- /*
- * Wait for some work to do.
- */
- nready = select(maxfd + 1, &rfds, &wfds, NULL, NULL);
- if (nready < 0)
- {
- if (errno == EINTR)
- continue;
- elog(LOG, "PGSTATBUFF: select() failed: %m");
- exit(1);
- }
-
- /*
- * If there is a message on the socket, read it and check for
- * validity.
- */
- if (FD_ISSET(pgStatSock, &rfds))
- {
- fromlen = sizeof(fromaddr);
- len = recvfrom(pgStatSock,
- &input_buffer, sizeof(PgStat_Msg), 0,
- (struct sockaddr *) &fromaddr, &fromlen);
- if (len < 0)
- {
- elog(LOG, "PGSTATBUFF: recvfrom() failed: %m");
- exit(1);
- }
-
- /*
- * We ignore messages that are smaller than our common header
- */
- if (len < sizeof(PgStat_MsgHdr))
- continue;
-
- /*
- * The received length must match the length in the header
- */
- if (input_buffer.msg_hdr.m_size != len)
- continue;
-
- /*
- * The source address of the packet must be our own socket.
- * This ensures that only real hackers or our own backends
- * tell us something. (This should be redundant with a
- * kernel-level check due to having used connect(), but let's
- * do it anyway.)
- */
- if (fromaddr.sin_addr.s_addr != pgStatAddr.sin_addr.s_addr)
- continue;
- if (fromaddr.sin_port != pgStatAddr.sin_port)
- continue;
-
- /*
- * O.K. - we accept this message. Copy it to the circular
- * msgbuffer.
- */
- frm = 0;
- while (len > 0)
- {
- xfr = PGSTAT_RECVBUFFERSZ - msg_recv;
- if (xfr > len)
- xfr = len;
- Assert(xfr > 0);
- memcpy(msgbuffer + msg_recv,
- ((char *) &input_buffer) + frm,
- xfr);
- msg_recv += xfr;
- if (msg_recv == PGSTAT_RECVBUFFERSZ)
- msg_recv = 0;
- msg_have += xfr;
- frm += xfr;
- len -= xfr;
- }
- }
-
- /*
- * If the collector is ready to receive, write some data into his
- * pipe. We may or may not be able to write all that we have.
- *
- * NOTE: if what we have is less than PIPE_BUF bytes but more than
- * the space available in the pipe buffer, most kernels will
- * refuse to write any of it, and will return EAGAIN. This means
- * we will busy-loop until the situation changes (either because
- * the collector caught up, or because more data arrives so that
- * we have more than PIPE_BUF bytes buffered). This is not good,
- * but is there any way around it? We have no way to tell when
- * the collector has caught up...
- */
- if (FD_ISSET(writePipe, &wfds))
- {
- xfr = PGSTAT_RECVBUFFERSZ - msg_send;
- if (xfr > msg_have)
- xfr = msg_have;
- Assert(xfr > 0);
- len = write(writePipe, msgbuffer + msg_send, xfr);
- if (len < 0)
- {
- if (errno == EINTR || errno == EAGAIN)
- continue; /* not enough space in pipe */
- elog(LOG, "PGSTATBUFF: write() failed: %m");
- exit(1);
- }
- /* NB: len < xfr is okay */
- msg_send += len;
- if (msg_send == PGSTAT_RECVBUFFERSZ)
- msg_send = 0;
- msg_have -= len;
- }
-
- /*
- * Make sure we forwarded all messages before we check for
- * Postmaster termination.
- */
- if (msg_have != 0 || FD_ISSET(pgStatSock, &rfds))
- continue;
-
- /*
- * If the pipe from the postmaster is ready for reading, the
- * kernel must have closed it on exit() (the postmaster never
- * really writes to it). So we've done our job.
- */
- if (FD_ISSET(pmPipe, &rfds))
- exit(0);
- }
-}
-
-static void
-pgstat_die(SIGNAL_ARGS)
-{
- exit(1);
-}
-
-
-/* ----------
- * pgstat_add_backend() -
- *
- * Support function to keep our backend list up to date.
- * ----------
- */
-static int
-pgstat_add_backend(PgStat_MsgHdr *msg)
-{
- PgStat_StatDBEntry *dbentry;
- PgStat_StatBeEntry *beentry;
- PgStat_StatBeDead *deadbe;
- bool found;
-
- /*
- * Check that the backend ID is valid
- */
- if (msg->m_backendid < 1 || msg->m_backendid > MaxBackends)
- {
- elog(LOG, "PGSTAT: Invalid backend ID %d", msg->m_backendid);
- return -1;
- }
-
- /*
- * Get the slot for this backendid.
- */
- beentry = &pgStatBeTable[msg->m_backendid - 1];
- if (beentry->databaseid != InvalidOid)
- {
- /*
- * If the slot contains the PID of this backend, everything is
- * fine and we got nothing to do.
- */
- if (beentry->procpid == msg->m_procpid)
- return 0;
- }
-
- /*
- * Lookup if this backend is known to be dead. This can be caused due
- * to messages arriving in the wrong order - i.e. Postmaster's BETERM
- * message might have arrived before we received all the backends
- * stats messages, or even a new backend with the same backendid was
- * faster in sending his BESTART.
- *
- * If the backend is known to be dead, we ignore this add.
- */
- deadbe = (PgStat_StatBeDead *) hash_search(pgStatBeDead,
- (void *) &(msg->m_procpid),
- HASH_FIND, NULL);
- if (deadbe)
- return 1;
-
- /*
- * Backend isn't known to be dead. If it's slot is currently used, we
- * have to kick out the old backend.
- */
- if (beentry->databaseid != InvalidOid)
- pgstat_sub_backend(beentry->procpid);
-
- /*
- * Put this new backend into the slot.
- */
- beentry->databaseid = msg->m_databaseid;
- beentry->procpid = msg->m_procpid;
- beentry->userid = msg->m_userid;
- MemSet(beentry->activity, 0, PGSTAT_ACTIVITY_SIZE);
-
- /*
- * Lookup or create the database entry for this backends DB.
- */
- dbentry = (PgStat_StatDBEntry *) hash_search(pgStatDBHash,
- (void *) &(msg->m_databaseid),
- HASH_ENTER, &found);
- if (dbentry == NULL)
- {
- elog(LOG, "PGSTAT: DB hash table out of memory - abort");
- exit(1);
- }
-
- /*
- * If not found, initialize the new one.
- */
- if (!found)
- {
- HASHCTL hash_ctl;
-
- dbentry->tables = NULL;
- dbentry->n_xact_commit = 0;
- dbentry->n_xact_rollback = 0;
- dbentry->n_blocks_fetched = 0;
- dbentry->n_blocks_hit = 0;
- dbentry->n_connects = 0;
- dbentry->destroy = 0;
-
- memset(&hash_ctl, 0, sizeof(hash_ctl));
- hash_ctl.keysize = sizeof(Oid);
- hash_ctl.entrysize = sizeof(PgStat_StatTabEntry);
- hash_ctl.hash = tag_hash;
- dbentry->tables = hash_create("Per-database table",
- PGSTAT_TAB_HASH_SIZE,
- &hash_ctl,
- HASH_ELEM | HASH_FUNCTION);
- if (dbentry->tables == NULL)
- {
- elog(LOG, "PGSTAT: failed to initialize hash table for "
- "new database entry");
- exit(1);
- }
- }
-
- /*
- * Count number of connects to the database
- */
- dbentry->n_connects++;
-
- return 0;
-}
-
-
-/* ----------
- * pgstat_sub_backend() -
- *
- * Remove a backend from the actual backends list.
- * ----------
- */
-static void
-pgstat_sub_backend(int procpid)
-{
- int i;
- PgStat_StatBeDead *deadbe;
- bool found;
-
- /*
- * Search in the known-backends table for the slot containing this
- * PID.
- */
- for (i = 0; i < MaxBackends; i++)
- {
- if (pgStatBeTable[i].databaseid != InvalidOid &&
- pgStatBeTable[i].procpid == procpid)
- {
- /*
- * That's him. Add an entry to the known to be dead backends.
- * Due to possible misorder in the arrival of UDP packets it's
- * possible that even if we know the backend is dead, there
- * could still be messages queued that arrive later. Those
- * messages must not cause our number of backends statistics
- * to get screwed up, so we remember for a couple of seconds
- * that this PID is dead and ignore them (only the counting of
- * backends, not the table access stats they sent).
- */
- deadbe = (PgStat_StatBeDead *) hash_search(pgStatBeDead,
- (void *) &procpid,
- HASH_ENTER, &found);
- if (deadbe == NULL)
- {
- elog(LOG, "PGSTAT: dead backend hash table out of memory");
- exit(1);
- }
- if (!found)
- {
- deadbe->backendid = i + 1;
- deadbe->destroy = PGSTAT_DESTROY_COUNT;
- }
-
- /*
- * Declare the backend slot empty.
- */
- pgStatBeTable[i].databaseid = InvalidOid;
- return;
- }
- }
-
- /*
- * No big problem if not found. This can happen if UDP messages arrive
- * out of order here.
- */
-}
-
-
-/* ----------
- * pgstat_write_statsfile() -
- *
- * Tell the news.
- * ----------
- */
-static void
-pgstat_write_statsfile(void)
-{
- HASH_SEQ_STATUS hstat;
- HASH_SEQ_STATUS tstat;
- PgStat_StatDBEntry *dbentry;
- PgStat_StatTabEntry *tabentry;
- PgStat_StatBeDead *deadbe;
- FILE *fpout;
- int i;
-
- /*
- * Open the statistics temp file to write out the current values.
- */
- fpout = fopen(pgStat_tmpfname, PG_BINARY_W);
- if (fpout == NULL)
- {
- elog(LOG, "PGSTAT: cannot open temp stats file %s: %m",
- pgStat_tmpfname);
- return;
- }
-
- /*
- * Walk through the database table.
- */
- hash_seq_init(&hstat, pgStatDBHash);
- while ((dbentry = (PgStat_StatDBEntry *) hash_seq_search(&hstat)) != NULL)
- {
- /*
- * If this database is marked destroyed, count down and do so if
- * it reaches 0.
- */
- if (dbentry->destroy > 0)
- {
- if (--(dbentry->destroy) == 0)
- {
- if (dbentry->tables != NULL)
- hash_destroy(dbentry->tables);
-
- if (hash_search(pgStatDBHash,
- (void *) &(dbentry->databaseid),
- HASH_REMOVE, NULL) == NULL)
- {
- elog(LOG, "PGSTAT: database hash table corrupted "
- "during cleanup - abort");
- exit(1);
- }
- }
-
- /*
- * Don't include statistics for it.
- */
- continue;
- }
-
- /*
- * Write out the DB line including the number of life backends.
- */
- fputc('D', fpout);
- fwrite(dbentry, sizeof(PgStat_StatDBEntry), 1, fpout);
-
- /*
- * Walk through the databases access stats per table.
- */
- hash_seq_init(&tstat, dbentry->tables);
- while ((tabentry = (PgStat_StatTabEntry *) hash_seq_search(&tstat)) != NULL)
- {
- /*
- * If table entry marked for destruction, same as above for
- * the database entry.
- */
- if (tabentry->destroy > 0)
- {
- if (--(tabentry->destroy) == 0)
- {
- if (hash_search(dbentry->tables,
- (void *) &(tabentry->tableid),
- HASH_REMOVE, NULL) == NULL)
- {
- elog(LOG, "PGSTAT: tables hash table for "
- "database %d corrupted during "
- "cleanup - abort",
- dbentry->databaseid);
- exit(1);
- }
- }
- continue;
- }
-
- /*
- * At least we think this is still a life table. Print it's
- * access stats.
- */
- fputc('T', fpout);
- fwrite(tabentry, sizeof(PgStat_StatTabEntry), 1, fpout);
- }
-
- /*
- * Mark the end of this DB
- */
- fputc('d', fpout);
- }
-
- /*
- * Write out the known running backends to the stats file.
- */
- i = MaxBackends;
- fputc('M', fpout);
- fwrite(&i, sizeof(i), 1, fpout);
-
- for (i = 0; i < MaxBackends; i++)
- {
- if (pgStatBeTable[i].databaseid != InvalidOid)
- {
- fputc('B', fpout);
- fwrite(&pgStatBeTable[i], sizeof(PgStat_StatBeEntry), 1, fpout);
- }
- }
-
- /*
- * No more output to be done. Close the temp file and replace the old
- * pgstat.stat with it's content.
- */
- fputc('E', fpout);
- if (fclose(fpout) < 0)
- {
- elog(LOG, "PGSTAT: Error closing temp stats file %s: %m",
- pgStat_tmpfname);
- }
- else
- {
- if (rename(pgStat_tmpfname, pgStat_fname) < 0)
- {
- elog(LOG, "PGSTAT: Cannot rename temp stats file %s: %m",
- pgStat_fname);
- }
- }
-
- /*
- * Clear out the dead backends table
- */
- hash_seq_init(&hstat, pgStatBeDead);
- while ((deadbe = (PgStat_StatBeDead *) hash_seq_search(&hstat)) != NULL)
- {
- /*
- * Count down the destroy delay and remove entries where it
- * reaches 0.
- */
- if (--(deadbe->destroy) <= 0)
- {
- if (hash_search(pgStatBeDead,
- (void *) &(deadbe->procpid),
- HASH_REMOVE, NULL) == NULL)
- {
- elog(LOG, "PGSTAT: dead backend hash table corrupted "
- "during cleanup - abort");
- exit(1);
- }
- }
- }
-}
-
-
-/* ----------
- * pgstat_read_statsfile() -
- *
- * Reads in an existing statistics collector and initializes the
- * databases hash table (who's entries point to the tables hash tables)
- * and the current backend table.
- * ----------
- */
-static void
-pgstat_read_statsfile(HTAB **dbhash, Oid onlydb,
- PgStat_StatBeEntry **betab, int *numbackends)
-{
- PgStat_StatDBEntry *dbentry;
- PgStat_StatDBEntry dbbuf;
- PgStat_StatTabEntry *tabentry;
- PgStat_StatTabEntry tabbuf;
- HASHCTL hash_ctl;
- HTAB *tabhash = NULL;
- FILE *fpin;
- int maxbackends = 0;
- int havebackends = 0;
- bool found;
- MemoryContext use_mcxt;
- int mcxt_flags;
-
- /*
- * If running in the collector we use the DynaHashCxt memory context.
- * If running in a backend, we use the TopTransactionContext instead,
- * so the caller must only know the last XactId when this call
- * happened to know if his tables are still valid or already gone!
- */
- if (pgStatRunningInCollector)
- {
- use_mcxt = NULL;
- mcxt_flags = 0;
- }
- else
- {
- use_mcxt = TopTransactionContext;
- mcxt_flags = HASH_CONTEXT;
- }
-
- /*
- * Create the DB hashtable
- */
- memset(&hash_ctl, 0, sizeof(hash_ctl));
- hash_ctl.keysize = sizeof(Oid);
- hash_ctl.entrysize = sizeof(PgStat_StatDBEntry);
- hash_ctl.hash = tag_hash;
- hash_ctl.hcxt = use_mcxt;
- *dbhash = hash_create("Databases hash", PGSTAT_DB_HASH_SIZE, &hash_ctl,
- HASH_ELEM | HASH_FUNCTION | mcxt_flags);
- if (*dbhash == NULL)
- {
- if (pgStatRunningInCollector)
- {
- elog(LOG, "PGSTAT: Creation of DB hash table failed");
- exit(1);
- }
- /* in backend, can do normal error */
- elog(ERROR, "PGSTAT: Creation of DB hash table failed");
- }
-
- /*
- * Initialize the number of known backends to zero, just in case we do
- * a silent error return below.
- */
- if (numbackends != NULL)
- *numbackends = 0;
- if (betab != NULL)
- *betab = NULL;
-
- /*
- * Try to open the status file. If it doesn't exist, the backends
- * simply return zero for anything and the collector simply starts
- * from scratch with empty counters.
- */
- if ((fpin = fopen(pgStat_fname, PG_BINARY_R)) == NULL)
- return;
-
- /*
- * We found an existing collector stats file. Read it and put all the
- * hashtable entries into place.
- */
- for (;;)
- {
- switch (fgetc(fpin))
- {
- /*
- * 'D' A PgStat_StatDBEntry struct describing a database
- * follows. Subsequently, zero to many 'T' entries will
- * follow until a 'd' is encountered.
- */
- case 'D':
- if (fread(&dbbuf, 1, sizeof(dbbuf), fpin) != sizeof(dbbuf))
- {
- if (pgStatRunningInCollector)
- {
- elog(LOG, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- else
- {
- elog(WARNING, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- }
-
- /*
- * Add to the DB hash
- */
- dbentry = (PgStat_StatDBEntry *) hash_search(*dbhash,
- (void *) &dbbuf.databaseid,
- HASH_ENTER, &found);
- if (dbentry == NULL)
- {
- if (pgStatRunningInCollector)
- {
- elog(LOG, "PGSTAT: DB hash table out of memory");
- exit(1);
- }
- else
- {
- fclose(fpin);
- elog(ERROR, "PGSTAT: DB hash table out of memory");
- }
- }
- if (found)
- {
- if (pgStatRunningInCollector)
- {
- elog(LOG, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- else
- {
- elog(WARNING, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- }
-
- memcpy(dbentry, &dbbuf, sizeof(PgStat_StatDBEntry));
- dbentry->tables = NULL;
- dbentry->destroy = 0;
- dbentry->n_backends = 0;
-
- /*
- * Don't collect tables if not the requested DB
- */
- if (onlydb != InvalidOid && onlydb != dbbuf.databaseid)
- break;
-
-
- memset(&hash_ctl, 0, sizeof(hash_ctl));
- hash_ctl.keysize = sizeof(Oid);
- hash_ctl.entrysize = sizeof(PgStat_StatTabEntry);
- hash_ctl.hash = tag_hash;
- hash_ctl.hcxt = use_mcxt;
- dbentry->tables = hash_create("Per-database table",
- PGSTAT_TAB_HASH_SIZE,
- &hash_ctl,
- HASH_ELEM | HASH_FUNCTION | mcxt_flags);
- if (dbentry->tables == NULL)
- {
- if (pgStatRunningInCollector)
- {
- elog(LOG, "PGSTAT: failed to initialize "
- "hash table for new database entry");
- exit(1);
- }
- else
- {
- fclose(fpin);
- elog(ERROR, "PGSTAT: failed to initialize "
- "hash table for new database entry");
- }
- }
-
- /*
- * Arrange that following 'T's add entries to this
- * databases tables hash table.
- */
- tabhash = dbentry->tables;
- break;
-
- /*
- * 'd' End of this database.
- */
- case 'd':
- tabhash = NULL;
- break;
-
- /*
- * 'T' A PgStat_StatTabEntry follows.
- */
- case 'T':
- if (fread(&tabbuf, 1, sizeof(tabbuf), fpin) != sizeof(tabbuf))
- {
- if (pgStatRunningInCollector)
- {
- elog(LOG, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- else
- {
- elog(WARNING, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- }
-
- /*
- * Skip if table belongs to a not requested database.
- */
- if (tabhash == NULL)
- break;
-
- tabentry = (PgStat_StatTabEntry *) hash_search(tabhash,
- (void *) &tabbuf.tableid,
- HASH_ENTER, &found);
- if (tabentry == NULL)
- {
- if (pgStatRunningInCollector)
- {
- elog(LOG, "PGSTAT: Tab hash table out of memory");
- exit(1);
- }
- else
- {
- fclose(fpin);
- elog(ERROR, "PGSTAT: Tab hash table out of memory");
- }
- }
-
- if (found)
- {
- if (pgStatRunningInCollector)
- {
- elog(LOG, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- else
- {
- elog(WARNING, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- }
-
- memcpy(tabentry, &tabbuf, sizeof(tabbuf));
- break;
-
- /*
- * 'M' The maximum number of backends to expect follows.
- */
- case 'M':
- if (betab == NULL || numbackends == NULL)
- {
- fclose(fpin);
- return;
- }
- if (fread(&maxbackends, 1, sizeof(maxbackends), fpin) !=
- sizeof(maxbackends))
- {
- if (pgStatRunningInCollector)
- {
- elog(LOG, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- else
- {
- elog(WARNING, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- }
- if (maxbackends == 0)
- {
- fclose(fpin);
- return;
- }
-
- /*
- * Allocate space (in TopTransactionContext too) for the
- * backend table.
- */
- if (use_mcxt == NULL)
- *betab = (PgStat_StatBeEntry *) malloc(
- sizeof(PgStat_StatBeEntry) * maxbackends);
- else
- *betab = (PgStat_StatBeEntry *) MemoryContextAlloc(
- use_mcxt,
- sizeof(PgStat_StatBeEntry) * maxbackends);
- break;
-
- /*
- * 'B' A PgStat_StatBeEntry follows.
- */
- case 'B':
- if (betab == NULL || numbackends == NULL)
- {
- fclose(fpin);
- return;
- }
- if (*betab == NULL)
- {
- fclose(fpin);
- return;
- }
-
- /*
- * Read it directly into the table.
- */
- if (fread(&(*betab)[havebackends], 1,
- sizeof(PgStat_StatBeEntry), fpin) !=
- sizeof(PgStat_StatBeEntry))
- {
- if (pgStatRunningInCollector)
- {
- elog(LOG, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- else
- {
- elog(WARNING, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- }
-
- /*
- * Count backends per database here.
- */
- dbentry = (PgStat_StatDBEntry *) hash_search(*dbhash,
- (void *) &((*betab)[havebackends].databaseid),
- HASH_FIND, NULL);
- if (dbentry)
- dbentry->n_backends++;
-
- havebackends++;
- if (numbackends != 0)
- *numbackends = havebackends;
- if (havebackends >= maxbackends)
- {
- fclose(fpin);
- return;
- }
- break;
-
- /*
- * 'E' The EOF marker of a complete stats file.
- */
- case 'E':
- fclose(fpin);
- return;
-
- default:
- if (pgStatRunningInCollector)
- {
- elog(LOG, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- else
- {
- elog(WARNING, "PGSTAT: corrupted pgstat.stat file");
- fclose(fpin);
- return;
- }
- }
- }
-
- fclose(fpin);
-}
-
-
-/* ----------
- * pgstat_recv_bestart() -
- *
- * Process a backend starup message.
- * ----------
- */
-static void
-pgstat_recv_bestart(PgStat_MsgBestart *msg, int len)
-{
- pgstat_add_backend(&msg->m_hdr);
-}
-
-
-/* ----------
- * pgstat_recv_beterm() -
- *
- * Process a backend termination message.
- * ----------
- */
-static void
-pgstat_recv_beterm(PgStat_MsgBeterm *msg, int len)
-{
- pgstat_sub_backend(msg->m_hdr.m_procpid);
-}
-
-
-/* ----------
- * pgstat_recv_activity() -
- *
- * Remember what the backend is doing.
- * ----------
- */
-static void
-pgstat_recv_activity(PgStat_MsgActivity *msg, int len)
-{
- /*
- * Here we check explicitly for 0 return, since we don't want to
- * mangle the activity of an active backend by a delayed packed from a
- * dead one.
- */
- if (pgstat_add_backend(&msg->m_hdr) != 0)
- return;
-
- strncpy(pgStatBeTable[msg->m_hdr.m_backendid - 1].activity,
- msg->m_what, PGSTAT_ACTIVITY_SIZE);
-}
-
-
-/* ----------
- * pgstat_recv_tabstat() -
- *
- * Count what the backend has done.
- * ----------
- */
-static void
-pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len)
-{
- PgStat_TableEntry *tabmsg = &(msg->m_entry[0]);
- PgStat_StatDBEntry *dbentry;
- PgStat_StatTabEntry *tabentry;
- int i;
- bool found;
-
- /*
- * Make sure the backend is counted for.
- */
- if (pgstat_add_backend(&msg->m_hdr) < 0)
- return;
-
- /*
- * Lookup the database in the hashtable.
- */
- dbentry = (PgStat_StatDBEntry *) hash_search(pgStatDBHash,
- (void *) &(msg->m_hdr.m_databaseid),
- HASH_FIND, NULL);
- if (!dbentry)
- return;
-
- /*
- * If the database is marked for destroy, this is a delayed UDP packet
- * and not worth being counted.
- */
- if (dbentry->destroy > 0)
- return;
-
- dbentry->n_xact_commit += (PgStat_Counter) (msg->m_xact_commit);
- dbentry->n_xact_rollback += (PgStat_Counter) (msg->m_xact_rollback);
-
- /*
- * Process all table entries in the message.
- */
- for (i = 0; i < msg->m_nentries; i++)
- {
- tabentry = (PgStat_StatTabEntry *) hash_search(dbentry->tables,
- (void *) &(tabmsg[i].t_id),
- HASH_ENTER, &found);
- if (tabentry == NULL)
- {
- elog(LOG, "PGSTAT: tables hash table out of memory for "
- "database %d - abort", dbentry->databaseid);
- exit(1);
- }
-
- if (!found)
- {
- /*
- * If it's a new table entry, initialize counters to the
- * values we just got.
- */
- tabentry->numscans = tabmsg[i].t_numscans;
- tabentry->tuples_returned = tabmsg[i].t_tuples_returned;
- tabentry->tuples_fetched = tabmsg[i].t_tuples_fetched;
- tabentry->tuples_inserted = tabmsg[i].t_tuples_inserted;
- tabentry->tuples_updated = tabmsg[i].t_tuples_updated;
- tabentry->tuples_deleted = tabmsg[i].t_tuples_deleted;
- tabentry->blocks_fetched = tabmsg[i].t_blocks_fetched;
- tabentry->blocks_hit = tabmsg[i].t_blocks_hit;
-
- tabentry->destroy = 0;
- }
- else
- {
- /*
- * Otherwise add the values to the existing entry.
- */
- tabentry->numscans += tabmsg[i].t_numscans;
- tabentry->tuples_returned += tabmsg[i].t_tuples_returned;
- tabentry->tuples_fetched += tabmsg[i].t_tuples_fetched;
- tabentry->tuples_inserted += tabmsg[i].t_tuples_inserted;
- tabentry->tuples_updated += tabmsg[i].t_tuples_updated;
- tabentry->tuples_deleted += tabmsg[i].t_tuples_deleted;
- tabentry->blocks_fetched += tabmsg[i].t_blocks_fetched;
- tabentry->blocks_hit += tabmsg[i].t_blocks_hit;
- }
-
- /*
- * And add the block IO to the database entry.
- */
- dbentry->n_blocks_fetched += tabmsg[i].t_blocks_fetched;
- dbentry->n_blocks_hit += tabmsg[i].t_blocks_hit;
- }
-}
-
-
-/* ----------
- * pgstat_recv_tabpurge() -
- *
- * Arrange for dead table removal.
- * ----------
- */
-static void
-pgstat_recv_tabpurge(PgStat_MsgTabpurge *msg, int len)
-{
- PgStat_StatDBEntry *dbentry;
- PgStat_StatTabEntry *tabentry;
- int i;
-
- /*
- * Make sure the backend is counted for.
- */
- if (pgstat_add_backend(&msg->m_hdr) < 0)
- return;
-
- /*
- * Lookup the database in the hashtable.
- */
- dbentry = (PgStat_StatDBEntry *) hash_search(pgStatDBHash,
- (void *) &(msg->m_hdr.m_databaseid),
- HASH_FIND, NULL);
- if (!dbentry)
- return;
-
- /*
- * If the database is marked for destroy, this is a delayed UDP packet
- * and the tables will go away at DB destruction.
- */
- if (dbentry->destroy > 0)
- return;
-
- /*
- * Process all table entries in the message.
- */
- for (i = 0; i < msg->m_nentries; i++)
- {
- tabentry = (PgStat_StatTabEntry *) hash_search(dbentry->tables,
- (void *) &(msg->m_tableid[i]),
- HASH_FIND, NULL);
- if (tabentry)
- tabentry->destroy = PGSTAT_DESTROY_COUNT;
- }
-}
-
-
-/* ----------
- * pgstat_recv_dropdb() -
- *
- * Arrange for dead database removal
- * ----------
- */
-static void
-pgstat_recv_dropdb(PgStat_MsgDropdb *msg, int len)
-{
- PgStat_StatDBEntry *dbentry;
-
- /*
- * Make sure the backend is counted for.
- */
- if (pgstat_add_backend(&msg->m_hdr) < 0)
- return;
-
- /*
- * Lookup the database in the hashtable.
- */
- dbentry = (PgStat_StatDBEntry *) hash_search(pgStatDBHash,
- (void *) &(msg->m_databaseid),
- HASH_FIND, NULL);
- if (!dbentry)
- return;
-
- /*
- * Mark the database for destruction.
- */
- dbentry->destroy = PGSTAT_DESTROY_COUNT;
-}
-
-
-/* ----------
- * pgstat_recv_dropdb() -
- *
- * Arrange for dead database removal
- * ----------
- */
-static void
-pgstat_recv_resetcounter(PgStat_MsgResetcounter *msg, int len)
-{
- HASHCTL hash_ctl;
- PgStat_StatDBEntry *dbentry;
-
- /*
- * Make sure the backend is counted for.
- */
- if (pgstat_add_backend(&msg->m_hdr) < 0)
- return;
-
- /*
- * Lookup the database in the hashtable.
- */
- dbentry = (PgStat_StatDBEntry *) hash_search(pgStatDBHash,
- (void *) &(msg->m_hdr.m_databaseid),
- HASH_FIND, NULL);
- if (!dbentry)
- return;
-
- /*
- * We simply throw away all the databases table entries by recreating
- * a new hash table for them.
- */
- if (dbentry->tables != NULL)
- hash_destroy(dbentry->tables);
-
- dbentry->tables = NULL;
- dbentry->n_xact_commit = 0;
- dbentry->n_xact_rollback = 0;
- dbentry->n_blocks_fetched = 0;
- dbentry->n_blocks_hit = 0;
- dbentry->n_connects = 0;
- dbentry->destroy = 0;
-
- memset(&hash_ctl, 0, sizeof(hash_ctl));
- hash_ctl.keysize = sizeof(Oid);
- hash_ctl.entrysize = sizeof(PgStat_StatTabEntry);
- hash_ctl.hash = tag_hash;
- dbentry->tables = hash_create("Per-database table",
- PGSTAT_TAB_HASH_SIZE,
- &hash_ctl,
- HASH_ELEM | HASH_FUNCTION);
- if (dbentry->tables == NULL)
- {
- elog(LOG, "PGSTAT: failed to reinitialize hash table for "
- "database entry");
- exit(1);
- }
-}