*
* pgstat.cpp
*
* All the statistics collector stuff hacked up in one big, ugly file.
*
* - 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 basis.
*
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
* Copyright (c) 2001-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 2021, openGauss Contributors
*
* IDENTIFICATION
* src/gausskernel/process/postmaster/pgstat.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "libpq/pqformat.h"
#include <fcntl.h>
#include <sys/param.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include "funcapi.h"
#include "utils/syscache.h"
#include "pgstat.h"
#include "access/gtm.h"
#include "access/heapam.h"
#include "utils/lsyscache.h"
#include "access/tableam.h"
#include "access/transam.h"
#include "access/twophase_rmgr.h"
#include "access/xact.h"
#include "catalog/pg_database.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_partition.h"
#include "catalog/pg_partition_fn.h"
#include "commands/vacuum.h"
#include "commands/user.h"
#include "gaussdb_version.h"
#include "foreign/fdwapi.h"
#include "gssignal/gs_signal.h"
#include "libpq/ip.h"
#include "libpq/libpq.h"
#include "libpq/pqsignal.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
#include "postmaster/postmaster.h"
#include "postmaster/pagewriter.h"
#include "replication/catchup.h"
#include "replication/walsender.h"
#include "storage/backendid.h"
#include "storage/smgr/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/lmgr.h"
#include "storage/pg_shmem.h"
#include "storage/procsignal.h"
#include "storage/procarray.h"
#include "storage/smgr/relfilenode_hash.h"
#include "threadpool/threadpool.h"
#include "utils/ascii.h"
#include "utils/atomic.h"
#include "utils/elog.h"
#include "utils/guc.h"
#include "utils/hotkey.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
#include "utils/rel.h"
#include "utils/rel_gs.h"
#include "utils/timestamp.h"
#include "utils/snapmgr.h"
#include "gssignal/gs_signal.h"
#include "utils/builtins.h"
#include "storage/proc.h"
#include "workload/workload.h"
#include "workload/gscgroup.h"
#include "utils/memprot.h"
#include "pgxc/poolmgr.h"
#include "access/multi_redo_api.h"
#include "instruments/instr_unique_sql.h"
#include "instruments/instr_event.h"
#include "instruments/instr_slow_query.h"
#include "instruments/instr_statement.h"
#include "instruments/instr_handle_mgr.h"
#ifdef ENABLE_MOT
#include "storage/mot/jit_exec.h"
#endif
#ifdef ENABLE_UT
#define static
#endif
#define pg_stat_relation(flag) (InvalidOid == (flag))
* Timer definitions.
* ----------
*/
#define PGSTAT_STAT_INTERVAL \
500
* updates; in milliseconds. */
#define PGSTAT_RETRY_DELAY \
10
* a new file; in milliseconds. */
#define PGSTAT_MAX_WAIT_TIME \
10000
* file update; in milliseconds. */
#define PGSTAT_INQ_INTERVAL \
640
* a new file; in milliseconds. */
#define PGSTAT_RESTART_INTERVAL \
60
* failed statistics collector; in \ \ \
* seconds. */
#define PGSTAT_POLL_LOOP_COUNT (PGSTAT_MAX_WAIT_TIME / PGSTAT_RETRY_DELAY)
#define PGSTAT_INQ_LOOP_COUNT (PGSTAT_INQ_INTERVAL / PGSTAT_RETRY_DELAY)
#define PGSTAT_MIN_RCVBUF (100 * 1024)
* 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
* Macros of the os statistic file system path.
* ----------
*/
#define JiffiesGetCentiSec(x) ((x) * (100 / HZ))
#define ProcPathMax 4096
#define VmStatFileReadBuffer 4096
#define SysFileSystemPath "/sys/devices/system"
#define SysCpuPath "/sys/devices/system/cpu/cpu%u"
#define ThreadSiblingFile "/sys/devices/system/cpu/cpu0/topology/thread_siblings"
#define CoreSiblingFile "/sys/devices/system/cpu/cpu0/topology/core_siblings"
extern void WLMGetCPUDataIndicator(PgBackendStatus*, WLMDataIndicator<int64>*);
* 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_initstats() when a relation is
* repeatedly opened during a transaction.
*/
#define TABSTAT_QUANTUM 100
typedef struct TabStatusArray {
struct TabStatusArray* tsa_next;
int tsa_used;
PgStat_TableStatus tsa_entries[TABSTAT_QUANTUM];
} TabStatusArray;
typedef struct TabStatHashEntry {
PgStat_StatTabKey t_key;
PgStat_TableStatus* tsa_entry;
} TabStatHashEntry;
* 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 u_sess->top_transaction_mem_cxt. This data structure is designed on the assumption
* that subxacts won't usually modify very many tables.
*/
typedef struct PgStat_SubXactStatus {
int nest_level;
struct PgStat_SubXactStatus* prev;
PgStat_TableXactStatus* first;
} PgStat_SubXactStatus;
typedef struct TwoPhasePgStatRecord {
PgStat_Counter tuples_inserted;
PgStat_Counter tuples_updated;
PgStat_Counter tuples_deleted;
PgStat_Counter tuples_inplace_updated;
PgStat_Counter inserted_pre_trunc;
PgStat_Counter updated_pre_trunc;
PgStat_Counter deleted_pre_trunc;
PgStat_Counter inplace_updated_pre_trunc;
PgStat_Counter tuples_inserted_accum;
PgStat_Counter tuples_updated_accum;
PgStat_Counter tuples_deleted_accum;
PgStat_Counter tuples_inplace_updated_accum;
Oid t_id;
bool t_shared;
bool t_truncated;
* if t_id is a parition oid , then t_statFlag is the corresponding
* partitioned table oid; if t_id is a non-parition oid, then t_statFlag is InvlaidOId
*/
uint32 t_statFlag;
} TwoPhasePgStatRecord;
* Original value for u_sess->stat_cxt.osStatDescArray. For most members,
* the values are fixed, and for got, we initialize it to false.
*/
const OSRunInfoDesc osStatDescArrayOrg[TOTAL_OS_RUN_INFO_TYPES] = {
{Int32GetNumberDatum, "NUM_CPUS", false, false, "Number of CPUs or processors available"},
{Int32GetNumberDatum,
"NUM_CPU_CORES",
false,
false,
"Number of CPU cores available (includes subcores of multicore CPUs as well as single-core CPUs)"},
{Int32GetNumberDatum,
"NUM_CPU_SOCKETS",
false,
false,
"Number of CPU sockets available (represents an absolute count of CPU chips on the system, regardless of "
"multithreading or multi-core architectures)"},
{Int64GetNumberDatum,
"IDLE_TIME",
true,
false,
"Number of hundredths of a second that a processor has been idle, totalled over all processors"},
{Int64GetNumberDatum,
"BUSY_TIME",
true,
false,
"Number of hundredths of a second that a processor has been busy executing user or kernel code, totalled over "
"all processors"},
{Int64GetNumberDatum,
"USER_TIME",
true,
false,
"Number of hundredths of a second that a processor has been busy executing user code, totalled over all "
"processors"},
{Int64GetNumberDatum,
"SYS_TIME",
true,
false,
"Number of hundredths of a second that a processor has been busy executing kernel code, totalled over all "
"processors"},
{Int64GetNumberDatum,
"IOWAIT_TIME",
true,
false,
"Number of hundredths of a second that a processor has been waiting for I/O to complete, totalled over all "
"processors"},
{Int64GetNumberDatum,
"NICE_TIME",
true,
false,
"Number of hundredths of a second that a processor has been busy executing low-priority user code, totalled "
"over all processors"},
{Int64GetNumberDatum,
"AVG_IDLE_TIME",
true,
false,
"Number of hundredths of a second that a processor has been idle, averaged over all processors"},
{Int64GetNumberDatum,
"AVG_BUSY_TIME",
true,
false,
"Number of hundredths of a second that a processor has been busy executing user or kernel code, averaged over "
"all processors"},
{Int64GetNumberDatum,
"AVG_USER_TIME",
true,
false,
"Number of hundredths of a second that a processor has been busy executing user code, averaged over all "
"processors"},
{Int64GetNumberDatum,
"AVG_SYS_TIME",
true,
false,
"Number of hundredths of a second that a processor has been busy executing kernel code, averaged over all "
"processors"},
{Int64GetNumberDatum,
"AVG_IOWAIT_TIME",
true,
false,
"Number of hundredths of a second that a processor has been waiting for I/O to complete, averaged over all "
"processors"},
{Int64GetNumberDatum,
"AVG_NICE_TIME",
true,
false,
"Number of hundredths of a second that a processor has been busy executing low-priority user code, averaged "
"over all processors"},
{Int64GetNumberDatum,
"VM_PAGE_IN_BYTES",
true,
false,
"Total number of bytes of data that have been paged in due to virtual memory paging"},
{Int64GetNumberDatum,
"VM_PAGE_OUT_BYTES",
true,
false,
"Total number of bytes of data that have been paged out due to virtual memory paging"},
{Float8GetNumberDatum,
"LOAD",
false,
false,
"Current number of processes that are either running or in the ready state, waiting to be selected by the "
"operating-system scheduler to run. On many platforms, this statistic reflects the average load over the past "
"minute."},
{Int64GetNumberDatum, "PHYSICAL_MEMORY_BYTES", false, false, "Total number of bytes of physical memory"}};
* Local function forward declarations
* ----------
*/
static void pgstat_exit(SIGNAL_ARGS);
static void pgstat_beshutdown_hook(int code, Datum arg);
static void pgstat_sighup_handler(SIGNAL_ARGS);
static PgStat_StatDBEntry* pgstat_get_db_entry(Oid databaseid, bool create);
static PgStat_StatTabEntry* pgstat_get_tab_entry(
PgStat_StatDBEntry* dbentry, Oid tableoid, bool create, uint32 statFlag);
static void pgstat_write_statsfile(bool permanent);
static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent);
static void backend_read_statsfile(void);
static void pgstat_send_tabstat(PgStat_MsgTabstat* tsmsg);
static void pgstat_send_funcstats(void);
static HTAB* pgstat_collect_oids(Oid catalogid);
static HTAB* pgstat_collect_tabkeys(void);
static PgStat_TableStatus* get_tabstat_entry(Oid rel_id, bool isshared, uint32 statFlag);
static void pgstat_collect_thread_status_setup_memcxt(void);
static void pgstat_collect_thread_status_clear_resource(void);
const char* pgstat_get_wait_io(WaitEventIO w);
const char* pgstat_get_wait_dms(WaitEventDMS w);
static void pgstat_setheader(PgStat_MsgHdr* hdr, StatMsgType mtype);
void pgstat_send(void* msg, int len);
static void pgstat_recv_inquiry(PgStat_MsgInquiry* 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);
static void pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter* msg, int len);
static void pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter* msg, int len);
static void pgstat_recv_autovac(PgStat_MsgAutovacStart* msg, int len);
static void pgstat_recv_vacuum(PgStat_MsgVacuum* msg, int len);
static void pgstat_recv_data_changed(PgStat_MsgDataChanged* msg, int len);
static void pgstat_recv_truncate(PgStat_MsgTruncate* msg, int len);
static void pgstat_recv_analyze(PgStat_MsgAnalyze* msg, int len);
static void pgstat_recv_bgwriter(PgStat_MsgBgWriter* msg, int len);
static void pgstat_recv_funcstat(PgStat_MsgFuncstat* msg, int len);
static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge* msg, int len);
static void pgstat_recv_recoveryconflict(const PgStat_MsgRecoveryConflict* msg);
static void pgstat_recv_deadlock(const PgStat_MsgDeadlock* msg);
static void pgstat_recv_tempfile(PgStat_MsgTempFile* msg, int len);
static void pgstat_recv_memReserved(const PgStat_MsgMemReserved* msg);
static void pgstat_recv_autovac_stat(PgStat_MsgAutovacStat* msg, int len);
static void PgstatRecvPrunestat(PgStat_MsgPrune* msg, int len);
static void pgstat_send_badblock_stat(void);
static void pgstat_recv_badblock_stat(PgStat_MsgBadBlock* msg, int len);
static bool checkSysFileSystem(void);
static bool checkLogicalCpu(uint32 cpuNum);
static uint32 parseSiblingFile(const char* path);
static void pgstat_recv_filestat(PgStat_MsgFile* msg, int len);
static void prepare_calculate(SqlRTInfoArray* sql_rt_info, int* counter);
static void pgstat_recv_sql_responstime(PgStat_SqlRT* msg, int len);
void initGlobalBadBlockStat();
static void initMySessionStatEntry(void);
static void initMySessionTimeEntry(void);
static void initMySessionMemoryEntry(void);
static void AttachMySessionStatEntry(void);
static void AttachMySessionTimeEntry(void);
static void AttachMySessionMemoryEntry(void);
static void endMySessionTimeEntry(int code, Datum arg);
static void DetachMySessionTimeEntry(volatile SessionTimeEntry* pEntry);
* Public functions called from postmaster follow
* ------------------------------------------------------------
*/
* pgstat_init() -
*
* Called from postmaster at startup. Create the resources required
* by the statistics collector process. If unable to do so, do not
* fail --- better to let the postmaster start with stats collection
* disabled.
* ----------
*/
void pgstat_init(void)
{
ACCEPT_TYPE_ARG3 alen;
struct addrinfo *addrs = NULL, *addr = NULL, hints;
int ret;
fd_set rset;
struct timeval tv;
char test_byte;
int sel_res;
int tries = 0;
struct sockaddr_storage pgStatAddr;
#define TESTBYTEVAL ((char)199)
* Create the UDP socket for sending and receiving statistic messages
*/
hints.ai_flags = AI_PASSIVE;
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = 0;
hints.ai_addrlen = 0;
hints.ai_addr = NULL;
hints.ai_canonname = NULL;
hints.ai_next = NULL;
ret = pg_getaddrinfo_all("localhost", NULL, &hints, &addrs);
if (ret || (addrs == NULL)) {
ereport(LOG, (errmsg("could not resolve \"localhost\": %s", gai_strerror(ret))));
goto startup_failed;
}
* On some platforms, pg_getaddrinfo_all() may return multiple addresses
* only one of which will actually work (eg, both IPv6 and IPv4 addresses
* when kernel will reject IPv6). Worse, the failure may occur at the
* bind() or perhaps even connect() stage. So we must loop through the
* results till we find a working combination. We will generate LOG
* messages, but no error, for bogus combinations.
*/
for (addr = addrs; addr; addr = addr->ai_next) {
#ifdef HAVE_UNIX_SOCKETS
if (addr->ai_family == AF_UNIX)
continue;
#endif
if (++tries > 1)
ereport(LOG, (errmsg("trying another address for the statistics collector")));
* Create the socket.
*/
if ((g_instance.stat_cxt.pgStatSock = socket(addr->ai_family, SOCK_DGRAM, 0)) == PGINVALID_SOCKET) {
ereport(LOG, (errcode_for_socket_access(), errmsg("could not create socket for statistics collector: %m")));
continue;
}
#ifdef F_SETFD
if (fcntl(g_instance.stat_cxt.pgStatSock, F_SETFD, FD_CLOEXEC) == -1) {
ereport(LOG,
(errcode_for_socket_access(), errmsg("setsockopt(FD_CLOEXEC) failed for statistics collector: %m")));
closesocket(g_instance.stat_cxt.pgStatSock);
g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET;
continue;
}
#endif
* Bind it to a kernel assigned port on localhost and get the assigned
* port via getsockname().
*/
if (bind(g_instance.stat_cxt.pgStatSock, addr->ai_addr, addr->ai_addrlen) < 0) {
ereport(LOG, (errcode_for_socket_access(), errmsg("could not bind socket for statistics collector: %m")));
closesocket(g_instance.stat_cxt.pgStatSock);
g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET;
continue;
}
alen = sizeof(pgStatAddr);
if (getsockname(g_instance.stat_cxt.pgStatSock, (struct sockaddr*)&pgStatAddr, &alen) < 0) {
ereport(LOG,
(errcode_for_socket_access(), errmsg("could not get address of socket for statistics collector: %m")));
closesocket(g_instance.stat_cxt.pgStatSock);
g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET;
continue;
}
* 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(g_instance.stat_cxt.pgStatSock, (struct sockaddr*)&pgStatAddr, alen) < 0) {
ereport(
LOG, (errcode_for_socket_access(), errmsg("could not connect socket for statistics collector: %m")));
closesocket(g_instance.stat_cxt.pgStatSock);
g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET;
continue;
}
* Try to send and receive a one-byte test message on the socket. This
* is to catch situations where the socket can be created but will not
* actually pass data (for instance, because kernel packet filtering
* rules prevent it).
*/
test_byte = TESTBYTEVAL;
retry1:
errno = 0;
if (send(g_instance.stat_cxt.pgStatSock, &test_byte, 1, 0) <= 0) {
if (errno == EINTR)
goto retry1;
ereport(LOG,
(errcode_for_socket_access(),
errmsg("could not send test message on socket for statistics collector: %m")));
closesocket(g_instance.stat_cxt.pgStatSock);
g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET;
continue;
}
* There could possibly be a little delay before the message can be
* received. We arbitrarily allow up to half a second before deciding
* it's broken.
* need a loop to handle EINTR
*/
for (;;) {
FD_ZERO(&rset);
if (g_instance.stat_cxt.pgStatSock + 1 > FD_SETSIZE) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("fd + 1 cannot be greater than FD_SETSIZE")));
}
FD_SET(g_instance.stat_cxt.pgStatSock, &rset);
tv.tv_sec = 0;
tv.tv_usec = 500000;
sel_res = select(g_instance.stat_cxt.pgStatSock + 1, &rset, NULL, NULL, &tv);
if (sel_res >= 0 || errno != EINTR)
break;
}
if (sel_res < 0) {
ereport(LOG, (errcode_for_socket_access(), errmsg("select() failed in statistics collector: %m")));
closesocket(g_instance.stat_cxt.pgStatSock);
g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET;
continue;
}
if (sel_res == 0 || !FD_ISSET(g_instance.stat_cxt.pgStatSock, &rset)) {
* This is the case we actually think is likely, so take pains to
* give a specific message for it.
*
* errno will not be set meaningfully here, so don't use it.
*/
ereport(LOG,
(errcode(ERRCODE_CONNECTION_FAILURE),
errmsg("test message did not get through on socket for statistics collector")));
closesocket(g_instance.stat_cxt.pgStatSock);
g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET;
continue;
}
test_byte++;
retry2:
errno = 0;
if (recv(g_instance.stat_cxt.pgStatSock, &test_byte, 1, 0) <= 0) {
if (errno == EINTR)
goto retry2;
ereport(LOG,
(errcode_for_socket_access(),
errmsg("could not receive test message on socket for statistics collector: %m")));
closesocket(g_instance.stat_cxt.pgStatSock);
g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET;
continue;
}
if (test_byte != TESTBYTEVAL) {
ereport(LOG,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("incorrect test message transmission on socket for statistics collector")));
closesocket(g_instance.stat_cxt.pgStatSock);
g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET;
continue;
}
break;
}
if ((addr == NULL) || g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET)
goto startup_failed;
* Set the socket to non-blocking IO. This ensures that if the collector
* falls behind, statistics messages will be discarded; backends won't
* block waiting to send messages to the collector.
*/
if (!pg_set_noblock(g_instance.stat_cxt.pgStatSock)) {
ereport(LOG,
(errcode_for_socket_access(), errmsg("could not set statistics collector socket to nonblocking mode: %m")));
goto startup_failed;
}
* Try to ensure that the socket's receive buffer is at least
* PGSTAT_MIN_RCVBUF bytes, so that it won't easily overflow and lose
* data. Use of UDP protocol means that we are willing to lose data under
* heavy load, but we don't want it to happen just because of ridiculously
* small default buffer sizes (such as 8KB on older Windows versions).
*/
{
int old_rcvbuf;
int new_rcvbuf;
ACCEPT_TYPE_ARG3 rcvbufsize = sizeof(old_rcvbuf);
if (getsockopt(g_instance.stat_cxt.pgStatSock, SOL_SOCKET, SO_RCVBUF, (char*)&old_rcvbuf, &rcvbufsize) < 0) {
elog(LOG, "getsockopt(SO_RCVBUF) failed: %m");
old_rcvbuf = 0;
}
new_rcvbuf = PGSTAT_MIN_RCVBUF;
if (old_rcvbuf < new_rcvbuf) {
if (setsockopt(
g_instance.stat_cxt.pgStatSock, SOL_SOCKET, SO_RCVBUF, (char*)&new_rcvbuf, sizeof(new_rcvbuf)) <
0) {
elog(LOG, "setsockopt(SO_RCVBUF) failed: %m");
}
}
}
pg_freeaddrinfo_all(hints.ai_family, addrs);
initGlobalBadBlockStat();
return;
startup_failed:
ereport(LOG, (errmsg("disabling statistics collector for lack of working socket")));
if (NULL != addrs)
pg_freeaddrinfo_all(hints.ai_family, addrs);
if (g_instance.stat_cxt.pgStatSock != PGINVALID_SOCKET)
closesocket(g_instance.stat_cxt.pgStatSock);
g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET;
* Adjust GUC variables to suppress useless activity, and for debugging
* purposes (seeing track_counts off is a clue that we failed here). We
* use PGC_S_OVERRIDE because there is no point in trying to turn it back
* on from postgresql.conf without a restart.
*/
SetConfigOption("track_counts", "off", PGC_INTERNAL, PGC_S_OVERRIDE);
}
* pgstat_reset_all() -
*
* Remove the stats file. This is currently used only if WAL
* recovery is needed after a crash.
*/
void pgstat_reset_all(void)
{
elog(LOG,
"[Pgstat] remove statfiles in %s, %s",
u_sess->stat_cxt.pgstat_stat_filename,
PGSTAT_STAT_PERMANENT_FILENAME);
unlink(u_sess->stat_cxt.pgstat_stat_filename);
unlink(PGSTAT_STAT_PERMANENT_FILENAME);
}
* pgstat_start() -
*
* Called from postmaster at startup or after an existing collector
* died. Attempt to fire up a fresh statistics collector.
*
* Returns PID of child process, or 0 if fail.
*
* Note: if fail, we will be called again from the postmaster main loop.
*/
ThreadId pgstat_start(void)
{
time_t curtime;
* Check that the socket is there, else pgstat_init failed and we can do
* nothing useful.
*/
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET)
return 0;
* Do nothing if too soon since last collector start. This is a safety
* valve to protect against continuous respawn attempts if the collector
* is dying immediately at launch. Note that since we will be re-called
* from the postmaster main loop, we will get another chance later.
*/
curtime = time(NULL);
if ((unsigned int)(curtime - g_instance.stat_cxt.last_pgstat_start_time) < (unsigned int)PGSTAT_RESTART_INTERVAL)
return 0;
g_instance.stat_cxt.last_pgstat_start_time = curtime;
return initialize_util_thread(PGSTAT);
}
void allow_immediate_pgstat_restart(void)
{
g_instance.stat_cxt.last_pgstat_start_time = 0;
}
* Public functions used by backends follow
* ------------------------------------------------------------
*/
* we assume that the uplimit memory size of u_sess->stat_cxt.pgStatTabList should be
* 2MB. that is, there are at most 128~136 TabStatusArray items. so
* 128 is chosen. if it's bigger than this limit, u_sess->stat_cxt.pgStatTabList should
* be destroyed.
*/
static inline bool pgstat_tablist_too_big(const int len)
{
return (len >= 128);
}
static void pgstat_free_tablist(void)
{
TabStatusArray* currItem = NULL;
TabStatusArray* nextItem = NULL;
currItem = u_sess->stat_cxt.pgStatTabList;
for (; currItem != NULL; currItem = nextItem) {
nextItem = currItem->tsa_next;
pfree_ext(currItem);
}
u_sess->stat_cxt.pgStatTabList = NULL;
}
* pgstat_report_stat() -
*
* Must be called by processes that performs DML: tcop/postgres.c, logical
* receiver processes, SPI worker, etc. to send the so far collected
* per-table and function usage statistics to the collector. Note that this
* is called only when not within a transaction, so it is fair to use
* ----------
*/
void pgstat_report_stat(bool force)
{
static const PgStat_TableCounts all_zeroes = {0};
TimestampTz now;
PgStat_MsgTabstat regular_msg;
PgStat_MsgTabstat shared_msg;
TabStatusArray* tsa = NULL;
int i;
int tablist_len = 0;
bool force_to_destory = false;
errno_t rc = EOK;
bool stat_no_change = ((u_sess->stat_cxt.pgStatTabList == NULL || u_sess->stat_cxt.pgStatTabList->tsa_used == 0) &&
!u_sess->stat_cxt.have_function_stats && !force);
if (stat_no_change) {
return;
}
* Don't send a message unless
* 1. it's been at least PGSTAT_STAT_INTERVAL msec since we last sent one
* 2. the caller wants to force stats out
* 3. it is on coordinator since it will wait for command too long in pooler mode
*/
now = GetCurrentTimestamp();
if (!force && !TimestampDifferenceExceeds(u_sess->stat_cxt.last_report, now, PGSTAT_STAT_INTERVAL))
return;
u_sess->stat_cxt.last_report = now;
* 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 = u_sess->proc_cxt.MyDatabaseId;
shared_msg.m_databaseid = InvalidOid;
regular_msg.m_nentries = 0;
shared_msg.m_nentries = 0;
DEBUG_MOD_START_TIMER(MOD_PGSTAT);
for (tsa = u_sess->stat_cxt.pgStatTabList; tsa != NULL; tsa = tsa->tsa_next) {
tablist_len++;
for (i = 0; i < tsa->tsa_used; i++) {
PgStat_TableStatus* entry = &tsa->tsa_entries[i];
PgStat_MsgTabstat* this_msg = NULL;
PgStat_TableEntry* this_ent = NULL;
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 : ®ular_msg;
this_ent = &this_msg->m_entry[this_msg->m_nentries];
this_ent->t_id = entry->t_id;
this_ent->t_statFlag = entry->t_statFlag;
rc =
memcpy_s(&this_ent->t_counts, sizeof(PgStat_TableCounts), &entry->t_counts, sizeof(PgStat_TableCounts));
securec_check(rc, "", "");
if ((unsigned int)++this_msg->m_nentries >= PGSTAT_NUM_TABENTRIES) {
pgstat_send_tabstat(this_msg);
this_msg->m_nentries = 0;
}
}
rc = memset_s(tsa->tsa_entries,
TABSTAT_QUANTUM * sizeof(PgStat_TableStatus),
0,
tsa->tsa_used * sizeof(PgStat_TableStatus));
securec_check(rc, "\0", "\0");
tsa->tsa_used = 0;
}
bool need_reset_counter =
(is_unique_sql_enabled() && IS_PGXC_DATANODE && u_sess->unique_sql_cxt.last_stat_counter != NULL);
if (need_reset_counter) {
rc = memset_s(
u_sess->unique_sql_cxt.last_stat_counter, sizeof(PgStat_TableCounts), 0, sizeof(PgStat_TableCounts));
securec_check(rc, "\0", "\0");
}
if (pgstat_tablist_too_big(tablist_len)) {
force_to_destory = true;
force = true;
}
hash_destroy(u_sess->stat_cxt.pgStatTabHash);
u_sess->stat_cxt.pgStatTabHash = NULL;
* Send partial messages. If force is true, make sure that any pending
* xact commit/abort gets counted, even if no table stats to send.
*/
if (regular_msg.m_nentries > 0 ||
(force && (u_sess->stat_cxt.pgStatXactCommit > 0 || u_sess->stat_cxt.pgStatXactRollback > 0)))
pgstat_send_tabstat(®ular_msg);
if (shared_msg.m_nentries > 0)
pgstat_send_tabstat(&shared_msg);
pgstat_send_funcstats();
pgstat_send_badblock_stat();
DEBUG_MOD_STOP_TIMER(MOD_PGSTAT, "send collected usage statistics to collector");
if (force_to_destory) {
pgstat_free_tablist();
}
}
* Subroutine for pgstat_report_stat: finish and send a tabstat message
*/
static void pgstat_send_tabstat(PgStat_MsgTabstat* tsmsg)
{
int n;
int len;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET)
return;
* Report and reset accumulated xact commit/rollback and I/O timings
* whenever we send a normal tabstat message
*/
if (OidIsValid(tsmsg->m_databaseid)) {
tsmsg->m_xact_commit = u_sess->stat_cxt.pgStatXactCommit;
tsmsg->m_xact_rollback = u_sess->stat_cxt.pgStatXactRollback;
tsmsg->m_block_read_time = u_sess->stat_cxt.pgStatBlockReadTime;
tsmsg->m_block_write_time = u_sess->stat_cxt.pgStatBlockWriteTime;
u_sess->stat_cxt.pgStatXactCommit = 0;
u_sess->stat_cxt.pgStatXactRollback = 0;
u_sess->stat_cxt.pgStatBlockReadTime = 0;
u_sess->stat_cxt.pgStatBlockWriteTime = 0;
} else {
tsmsg->m_xact_commit = 0;
tsmsg->m_xact_rollback = 0;
tsmsg->m_block_read_time = 0;
tsmsg->m_block_write_time = 0;
}
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);
}
* Subroutine for pgstat_report_stat: populate and send a function stat message
*/
static void pgstat_send_funcstats(void)
{
static const PgStat_FunctionCounts all_zeroes = {0};
PgStat_MsgFuncstat msg;
PgStat_BackendFunctionEntry* entry = NULL;
HASH_SEQ_STATUS fstat;
if (u_sess->stat_cxt.pgStatFunctions == NULL)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_FUNCSTAT);
msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId;
msg.m_nentries = 0;
hash_seq_init(&fstat, u_sess->stat_cxt.pgStatFunctions);
while ((entry = (PgStat_BackendFunctionEntry*)hash_seq_search(&fstat)) != NULL) {
PgStat_FunctionEntry* m_ent = NULL;
errno_t rc;
if (memcmp(&entry->f_counts, &all_zeroes, sizeof(PgStat_FunctionCounts)) == 0)
continue;
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 ((unsigned int)++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;
}
rc = memset_s(&entry->f_counts, sizeof(PgStat_FunctionCounts), 0, sizeof(PgStat_FunctionCounts));
securec_check(rc, "\0", "\0");
}
if (msg.m_nentries > 0)
pgstat_send(&msg, offsetof(PgStat_MsgFuncstat, m_entry[0]) + msg.m_nentries * sizeof(PgStat_FunctionEntry));
u_sess->stat_cxt.have_function_stats = false;
}
* pgstat_vacuum_stat() -
*
* Will tell the collector about objects he can get rid of.
* ----------
*/
void pgstat_vacuum_stat(void)
{
HTAB* htab = NULL;
PgStat_MsgTabpurge msg;
PgStat_MsgFuncpurge f_msg;
HASH_SEQ_STATUS hstat;
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatTabEntry* tabentry = NULL;
PgStat_StatFuncEntry* funcentry = NULL;
int len;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET)
return;
* If not done for this transaction, read the statistics collector stats
* file into some hash tables.
*/
backend_read_statsfile();
* Read pg_database and make a list of OIDs of all existing databases
*/
htab = pgstat_collect_oids(DatabaseRelationId);
* Search the database hash table for dead databases and tell the
* collector to drop them.
*/
hash_seq_init(&hstat, u_sess->stat_cxt.pgStatDBHash);
while ((dbentry = (PgStat_StatDBEntry*)hash_seq_search(&hstat)) != NULL) {
Oid dbid = dbentry->databaseid;
CHECK_FOR_INTERRUPTS();
if (OidIsValid(dbid) && hash_search(htab, (void*)&dbid, HASH_FIND, NULL) == NULL)
pgstat_drop_database(dbid);
}
hash_destroy(htab);
* Lookup our own database entry; if not found, nothing more to do.
*/
dbentry = (PgStat_StatDBEntry*)hash_search(
u_sess->stat_cxt.pgStatDBHash, (void*)&u_sess->proc_cxt.MyDatabaseId, HASH_FIND, NULL);
if (dbentry == NULL || dbentry->tables == NULL)
return;
* Similarly to above, make a list of all known relations in this DB.
*/
htab = pgstat_collect_tabkeys();
* Initialize our messages table counter to zero
*/
msg.m_nentries = 0;
* Check for all tables listed in stats hashtable if they still exist.
*/
hash_seq_init(&hstat, dbentry->tables);
while ((tabentry = (PgStat_StatTabEntry*)hash_seq_search(&hstat)) != NULL) {
PgStat_StatTabKey tabkey = tabentry->tablekey;
CHECK_FOR_INTERRUPTS();
if (hash_search(htab, (void*)(&tabkey), HASH_FIND, NULL) != NULL)
continue;
* Not there, so add this table's Oid to the message
*/
msg.m_entry[msg.m_nentries].m_tableid = tabkey.tableid;
msg.m_entry[msg.m_nentries].m_statFlag = tabkey.statFlag;
msg.m_nentries++;
* If the message is full, send it out and reinitialize to empty
*/
if ((unsigned int)msg.m_nentries >= PGSTAT_NUM_TABPURGE) {
len = offsetof(PgStat_MsgTabpurge, m_entry[0]) + msg.m_nentries * sizeof(PgStat_MsgTabEntry);
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TABPURGE);
msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId;
pgstat_send(&msg, len);
msg.m_nentries = 0;
}
}
* Send the rest
*/
if (msg.m_nentries > 0) {
len = offsetof(PgStat_MsgTabpurge, m_entry[0]) + msg.m_nentries * sizeof(PgStat_MsgTabEntry);
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TABPURGE);
msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId;
pgstat_send(&msg, len);
}
hash_destroy(htab);
* Now repeat the above steps for functions. However, we needn't bother
* in the common case where no function stats are being collected.
*/
if (dbentry->functions != NULL && hash_get_num_entries(dbentry->functions) > 0) {
htab = pgstat_collect_oids(ProcedureRelationId);
pgstat_setheader(&f_msg.m_hdr, PGSTAT_MTYPE_FUNCPURGE);
f_msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId;
f_msg.m_nentries = 0;
hash_seq_init(&hstat, dbentry->functions);
while ((funcentry = (PgStat_StatFuncEntry*)hash_seq_search(&hstat)) != NULL) {
Oid funcid = funcentry->functionid;
CHECK_FOR_INTERRUPTS();
if (hash_search(htab, (void*)&funcid, HASH_FIND, NULL) != NULL)
continue;
* Not there, so add this function's Oid to the message
*/
f_msg.m_functionid[f_msg.m_nentries++] = funcid;
* If the message is full, send it out and reinitialize to empty
*/
if ((unsigned int)f_msg.m_nentries >= PGSTAT_NUM_FUNCPURGE) {
len = offsetof(PgStat_MsgFuncpurge, m_functionid[0]) + f_msg.m_nentries * sizeof(Oid);
pgstat_send(&f_msg, len);
f_msg.m_nentries = 0;
}
}
* Send the rest
*/
if (f_msg.m_nentries > 0) {
len = offsetof(PgStat_MsgFuncpurge, m_functionid[0]) + f_msg.m_nentries * sizeof(Oid);
pgstat_send(&f_msg, len);
}
hash_destroy(htab);
}
}
* pgstat_collect_oids() -
*
* Collect the OIDs of all objects listed in the specified system catalog
* into a temporary hash table. Caller should hash_destroy the result
* when done with it. (However, we make the table in CurrentMemoryContext
* so that it will be freed properly in event of an error.)
* ----------
*/
static HTAB* pgstat_collect_oids(Oid catalogid)
{
HTAB* htab = NULL;
HASHCTL hash_ctl;
Relation rel;
TableScanDesc scan;
HeapTuple tup;
errno_t rc = EOK;
rc = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl));
securec_check(rc, "\0", "\0");
hash_ctl.keysize = sizeof(Oid);
hash_ctl.entrysize = sizeof(Oid);
hash_ctl.hash = oid_hash;
hash_ctl.hcxt = CurrentMemoryContext;
htab = hash_create(
"Temporary table of OIDs", PGSTAT_TAB_HASH_SIZE, &hash_ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
rel = heap_open(catalogid, AccessShareLock);
scan = tableam_scan_begin(rel, SnapshotNow, 0, NULL);
while ((tup = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
Oid thisoid = HeapTupleGetOid(tup);
CHECK_FOR_INTERRUPTS();
(void)hash_search(htab, (void*)&thisoid, HASH_ENTER, NULL);
}
tableam_scan_end(scan);
heap_close(rel, AccessShareLock);
return htab;
}
* @@GaussDB@@
* Target : data partition
* Brief : Collect the OIDs of all objects listed in the specified system catalog into
* : a temporary hash table. Caller should hash_destroy the result when
* : done with it. (However, we make the table in CurrentMemoryContext
* : so that it will be freed properly in event of an error.)
*/
static HTAB* pgstat_collect_tabkeys(void)
{
HTAB* htab = NULL;
HASHCTL hash_ctl;
Relation pgRelation;
Relation pgPartition;
TableScanDesc scan;
HeapTuple tuple;
Form_pg_partition partForm;
PgStat_StatTabKey tabkey;
errno_t rc = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl));
securec_check(rc, "\0", "\0");
hash_ctl.keysize = sizeof(PgStat_StatTabKey);
hash_ctl.entrysize = sizeof(PgStat_StatTabKey);
hash_ctl.hash = tag_hash;
hash_ctl.hcxt = CurrentMemoryContext;
htab = hash_create(
"Temporary table of OIDs", PGSTAT_TAB_HASH_SIZE, &hash_ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
tabkey.statFlag = InvalidOid;
pgRelation = heap_open(RelationRelationId, AccessShareLock);
scan = tableam_scan_begin(pgRelation, SnapshotNow, 0, NULL);
while ((tuple = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
tabkey.tableid = HeapTupleGetOid(tuple);
CHECK_FOR_INTERRUPTS();
(void)hash_search(htab, (void*)(&tabkey), HASH_ENTER, NULL);
}
tableam_scan_end(scan);
heap_close(pgRelation, AccessShareLock);
pgPartition = heap_open(PartitionRelationId, AccessShareLock);
scan = tableam_scan_begin(pgPartition, SnapshotNow, 0, NULL);
while ((tuple = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
partForm = (Form_pg_partition)GETSTRUCT(tuple);
if (partForm->parttype == PART_OBJ_TYPE_PARTED_TABLE)
continue;
tabkey.tableid = HeapTupleGetOid(tuple);
if (partForm->parttype == PART_OBJ_TYPE_TABLE_SUB_PARTITION) {
tabkey.statFlag = partid_get_parentid(partForm->parentid);
} else {
tabkey.statFlag = partForm->parentid;
}
CHECK_FOR_INTERRUPTS();
(void)hash_search(htab, (void*)(&tabkey), HASH_ENTER, NULL);
}
tableam_scan_end(scan);
heap_close(pgPartition, AccessShareLock);
return htab;
}
* 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 (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DROPDB);
msg.m_databaseid = databaseid;
pgstat_send(&msg, sizeof(msg));
}
* 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 (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET)
return;
msg.m_entry[0].m_tableid = relid;
msg.m_entry[0].m_statFlag = STATFLG_RELATION;
msg.m_nentries = 1;
len = offsetof(PgStat_MsgTabpurge, m_entry[0]) + sizeof(PgStat_MsgTabEntry);
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TABPURGE);
msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId;
pgstat_send(&msg, len);
}
#endif
* pgstat_reset_counters() -
*
* Tell the statistics collector to reset counters for our database.
* ----------
*/
void pgstat_reset_counters(void)
{
PgStat_MsgResetcounter msg;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET)
return;
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to reset statistics counters")));
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETCOUNTER);
msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId;
pgstat_send(&msg, sizeof(msg));
}
* pgstat_reset_shared_counters() -
*
* Tell the statistics collector to reset cluster-wide shared counters.
* ----------
*/
void pgstat_reset_shared_counters(const char* target)
{
PgStat_MsgResetsharedcounter msg;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET)
return;
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to reset statistics counters")));
if (strcmp(target, "bgwriter") == 0)
msg.m_resettarget = RESET_BGWRITER;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized reset target: \"%s\"", target),
errhint("Target must be \"bgwriter\".")));
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSHAREDCOUNTER);
pgstat_send(&msg, sizeof(msg));
}
* pgstat_reset_single_counter() -
*
* Tell the statistics collector to reset a single counter.
* ----------
*/
void pgstat_reset_single_counter(Oid p_objoid, Oid objoid, PgStat_Single_Reset_Type type)
{
PgStat_MsgResetsinglecounter msg;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET)
return;
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to reset statistics counters")));
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSINGLECOUNTER);
msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId;
msg.m_resettype = type;
msg.m_objectid = objoid;
msg.p_objectid = p_objoid;
pgstat_send(&msg, sizeof(msg));
}
* 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 u_sess->proc_cxt.MyDatabaseId;
* the db OID must be passed in, instead.
* ----------
*/
void pgstat_report_autovac(Oid dboid)
{
PgStat_MsgAutovacStart msg;
if (g_instance.stat_cxt.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_autovac_timeout(Oid tableoid, uint32 statFlag, bool shared)
{
PgStat_MsgAutovacStat msg;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_AUTOVAC_STAT);
msg.m_databaseid = shared ? InvalidOid : u_sess->proc_cxt.MyDatabaseId;
msg.m_tableoid = tableoid;
msg.m_statFlag = statFlag;
msg.m_autovacStat = AV_TIMEOUT;
pgstat_send(&msg, sizeof(msg));
}
* pgstat_report_vacuum() -
*
* Tell the collector about the table we just vacuumed.
* ---------
*/
void pgstat_report_vacuum(Oid tableoid, uint32 statFlag, bool shared, PgStat_Counter tuples)
{
PgStat_MsgVacuum msg;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_VACUUM);
msg.m_databaseid = shared ? InvalidOid : u_sess->proc_cxt.MyDatabaseId;
msg.m_tableoid = tableoid;
msg.m_statFlag = statFlag;
msg.m_autovacuum = IsAutoVacuumWorkerProcess() || IsFromAutoVacWoker();
msg.m_vacuumtime = GetCurrentTimestamp();
msg.m_tuples = tuples;
pgstat_send(&msg, sizeof(msg));
}
* PgstatReportPrunestat() -
*
* Tell the collector how many blocks we scanned and successfully pruned
* ---------
*/
void PgstatReportPrunestat(Oid tableoid, uint32 statFlag,
bool shared, PgStat_Counter scanned,
PgStat_Counter pruned)
{
PgStat_MsgPrune msg;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_PRUNESTAT);
msg.m_databaseid = shared ? InvalidOid : u_sess->proc_cxt.MyDatabaseId;
msg.m_tableoid = tableoid;
msg.m_statFlag = statFlag;
msg.m_scanned_blocks = scanned;
msg.m_pruned_blocks = pruned;
pgstat_send(&msg, sizeof(msg));
}
* pgstat_report_data_changed() -
*
* Tell the collector about the table we just insert/delete/update/
* copy/[exchange/truncate/drop] partition.
* ---------
*/
void pgstat_report_data_changed(Oid tableoid, uint32 statFlag, bool shared)
{
PgStat_MsgDataChanged msg;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DATA_CHANGED);
msg.m_databaseid = shared ? InvalidOid : u_sess->proc_cxt.MyDatabaseId;
msg.m_tableoid = tableoid;
msg.m_statFlag = statFlag;
msg.m_changed_time = GetCurrentTimestamp();
pgstat_send(&msg, sizeof(msg));
}
* pgstat_report_sql_rt() -
*
* Tell the collector about the sql responsetime.
* ---------
*/
void pgstat_report_sql_rt(uint64 UniqueSQLId, int64 start_time, int64 rt)
{
PgStat_SqlRT msg;
if (IS_SINGLE_NODE) {
return;
}
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.enable_instr_rt_percentile)
return;
if (!PMstateIsRun()) {
return;
}
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESPONSETIME);
msg.sqlRT.UniqueSQLId = UniqueSQLId;
msg.sqlRT.start_time = start_time;
msg.sqlRT.rt = rt;
pgstat_send(&msg, sizeof(msg));
}
void pgstat_report_process_percentile()
{
PgStat_PrsPtl msg;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_PROCESSPERCENTILE);
msg.now = GetCurrentTimestamp();
pgstat_send(&msg, sizeof(msg));
}
* pgstat_report_truncate() -
*
* Tell the collector about the table we just truncated.
* ---------
*/
void pgstat_report_truncate(Oid tableoid, uint32 statFlag, bool shared)
{
PgStat_MsgTruncate msg;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TRUNCATE);
msg.m_databaseid = shared ? InvalidOid : u_sess->proc_cxt.MyDatabaseId;
msg.m_tableoid = tableoid;
msg.m_statFlag = statFlag;
pgstat_send(&msg, sizeof(msg));
}
* pgstat_report_analyze() -
*
* Tell the collector about the table we just analyzed.
* --------
*/
void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples)
{
PgStat_MsgAnalyze msg;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.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.)
*/
if (rel->pgstat_info != NULL) {
PgStat_TableXactStatus* trans = NULL;
for (trans = rel->pgstat_info->trans; trans; trans = trans->upper) {
livetuples -= trans->tuples_inserted - trans->tuples_deleted;
deadtuples -= (trans->tuples_updated - trans->tuples_inplace_updated) + trans->tuples_deleted;
}
deadtuples -= rel->pgstat_info->t_counts.t_delta_dead_tuples;
livetuples = Max(livetuples, 0);
deadtuples = Max(deadtuples, 0);
}
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_ANALYZE);
msg.m_databaseid = rel->rd_rel->relisshared ? InvalidOid : u_sess->proc_cxt.MyDatabaseId;
msg.m_tableoid = RelationGetRelid(rel);
msg.m_statFlag = RelationIsSubPartitionOfSubPartitionTable(rel) ? rel->grandparentId : rel->parentId;
msg.m_autovacuum = IsAutoVacuumWorkerProcess() || IsFromAutoVacWoker();
msg.m_analyzetime = GetCurrentTimestamp();
msg.m_live_tuples = livetuples;
msg.m_dead_tuples = deadtuples;
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 (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RECOVERYCONFLICT);
msg.m_databaseid = u_sess->proc_cxt.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 (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DEADLOCK);
msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId;
pgstat_send(&msg, sizeof(msg));
}
* pgstat_report_tempfile() -
*
* Tell the collector about a temporary file.
* --------
*/
void pgstat_report_tempfile(size_t filesize)
{
PgStat_MsgTempFile msg;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TEMPFILE);
msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId;
msg.m_filesize = filesize;
pgstat_send(&msg, sizeof(msg));
}
* pgstat_report_memReserved() -
*
* Tell the collector about memory reservation
* ----------
*/
void pgstat_report_memReserved(int4 memReserved, int reserve_or_release)
{
PgStat_MsgMemReserved msg;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_MEMRESERVED);
msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId;
msg.m_memMbytes = memReserved;
msg.m_reserve_or_release = reserve_or_release;
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 (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET)
return;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DUMMY);
pgstat_send(&msg, sizeof(msg));
}
* pgstat_send_inquiry() -
*
* Notify collector that we need fresh data.
* ts specifies the minimum acceptable timestamp for the stats file.
* ----------
*/
static void pgstat_send_inquiry(TimestampTz ts)
{
PgStat_MsgInquiry msg;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_INQUIRY);
msg.inquiry_time = ts;
pgstat_send(&msg, sizeof(msg));
}
* Initialize function call usage data.
* Called by the executor before invoking a function.
*/
void pgstat_init_function_usage(FunctionCallInfoData* fcinfo, PgStat_FunctionCallUsage* fcu)
{
PgStat_BackendFunctionEntry* htabent = NULL;
bool found = false;
errno_t rc;
if (u_sess->attr.attr_common.pgstat_track_functions <= fcinfo->flinfo->fn_stats) {
fcu->fs = NULL;
return;
}
if (!u_sess->stat_cxt.pgStatFunctions) {
HASHCTL hash_ctl;
rc = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl));
securec_check(rc, "\0", "\0");
hash_ctl.keysize = sizeof(Oid);
hash_ctl.entrysize = sizeof(PgStat_BackendFunctionEntry);
hash_ctl.hash = oid_hash;
* hence the u_sess->stat_cxt.pgStatFunctions is used for whole session
* should use memory context under uess.top_mem_contxt here
*/
hash_ctl.hcxt = AllocSetContextCreate(u_sess->top_mem_cxt,
"Function stat hash",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
u_sess->stat_cxt.pgStatFunctions = hash_create(
"Function stat entries", PGSTAT_FUNCTION_HASH_SIZE, &hash_ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
}
htabent = (PgStat_BackendFunctionEntry*)hash_search(
u_sess->stat_cxt.pgStatFunctions, &fcinfo->flinfo->fn_oid, HASH_ENTER, &found);
if (!found) {
rc = memset_s(&htabent->f_counts, sizeof(PgStat_FunctionCounts), 0, sizeof(PgStat_FunctionCounts));
securec_check(rc, "\0", "\0");
}
fcu->fs = &htabent->f_counts;
fcu->save_f_total_time = htabent->f_counts.f_total_time;
fcu->save_total = u_sess->stat_cxt.total_func_time;
INSTR_TIME_SET_CURRENT(fcu->f_start);
}
* 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)
{
if (u_sess->stat_cxt.pgStatFunctions == NULL)
return NULL;
return (PgStat_BackendFunctionEntry*)hash_search(
u_sess->stat_cxt.pgStatFunctions, (void*)&func_id, HASH_FIND, NULL);
}
* 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;
if (fs == NULL)
return;
INSTR_TIME_SET_CURRENT(f_total);
INSTR_TIME_SUBTRACT(f_total, fcu->f_start);
f_others = u_sess->stat_cxt.total_func_time;
;
INSTR_TIME_SUBTRACT(f_others, fcu->save_total);
f_self = f_total;
INSTR_TIME_SUBTRACT(f_self, f_others);
INSTR_TIME_ADD(u_sess->stat_cxt.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);
if (finalize)
fs->f_numcalls++;
fs->f_total_time = f_total;
INSTR_TIME_ADD(fs->f_self_time, f_self);
u_sess->stat_cxt.have_function_stats = true;
}
* pgstat_initstats() -
*
* 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_initstats(Relation rel)
{
Oid rel_id = rel->rd_id;
char relkind = rel->rd_rel->relkind;
if (!(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW || relkind == RELKIND_INDEX ||
relkind == RELKIND_GLOBAL_INDEX || relkind == RELKIND_TOASTVALUE || RELKIND_IS_SEQUENCE(relkind))) {
rel->pgstat_info = NULL;
return;
}
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) {
rel->pgstat_info = NULL;
return;
}
rel->pgstat_info = get_tabstat_entry(rel_id, rel->rd_rel->relisshared, InvalidOid);
}
* Make sure pgStatTabList and pgStatTabHash are initialized.
*/
static void make_sure_stat_tab_initialized()
{
HASHCTL ctl;
errno_t rc;
if (u_sess->stat_cxt.pgStatTabList == NULL) {
u_sess->stat_cxt.pgStatTabList =
(TabStatusArray*)MemoryContextAllocZero(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), sizeof(TabStatusArray));
}
if (u_sess->stat_cxt.pgStatTabHash != NULL) {
return;
}
if (u_sess->stat_cxt.pgStatTabHashContext == NULL) {
u_sess->stat_cxt.pgStatTabHashContext =
AllocSetContextCreate(u_sess->top_mem_cxt, "PGStatLookupHashTableContext", ALLOCSET_DEFAULT_SIZES);
} else {
MemoryContextReset(u_sess->stat_cxt.pgStatTabHashContext);
}
rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl));
securec_check(rc, "\0", "\0");
ctl.keysize = sizeof(PgStat_StatTabKey);
ctl.entrysize = sizeof(TabStatHashEntry);
ctl.hash = tag_hash;
ctl.hcxt = u_sess->stat_cxt.pgStatTabHashContext;
u_sess->stat_cxt.pgStatTabHash = hash_create("pgstat sessionid to tsa_entry lookup hash table",
TABSTAT_QUANTUM,
&ctl,
HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
}
* get_tabstat_entry - find or create a PgStat_TableStatus entry for rel
*/
static PgStat_TableStatus* get_tabstat_entry(Oid rel_id, bool isshared, uint32 statFlag)
{
TabStatHashEntry* hash_entry = NULL;
PgStat_TableStatus* entry = NULL;
TabStatusArray* tsa = NULL;
bool found = false;
make_sure_stat_tab_initialized();
PgStat_StatTabKey rel_key;
rel_key.tableid = rel_id;
rel_key.statFlag = statFlag;
hash_entry = (TabStatHashEntry*)hash_search(u_sess->stat_cxt.pgStatTabHash, &rel_key, HASH_ENTER, &found);
if (found) {
return hash_entry->tsa_entry;
}
* `hash_entry` was just created and now we have to fill it.
* First make sure there is a free space in a last element of pgStatTabList.
*/
tsa = u_sess->stat_cxt.pgStatTabList;
while (tsa->tsa_used == TABSTAT_QUANTUM) {
if (tsa->tsa_next == NULL) {
tsa->tsa_next = (TabStatusArray*)MemoryContextAllocZero(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), sizeof(TabStatusArray));
}
tsa = tsa->tsa_next;
}
* Add an entry.
*/
entry = &tsa->tsa_entries[tsa->tsa_used];
entry->t_id = rel_id;
entry->t_shared = isshared;
entry->t_statFlag = statFlag;
tsa->tsa_used++;
* Add a corresponding entry to pgStatTabHash.
*/
hash_entry->tsa_entry = entry;
return entry;
}
* find_tabstat_entry - find any existing PgStat_TableStatus entry for rel
*
* If no entry, return NULL, don't create a new one
*/
PgStat_TableStatus* find_tabstat_entry(Oid rel_id, uint32 statFlag)
{
TabStatHashEntry* hash_entry;
* There are no entries at all.
*/
if (!u_sess->stat_cxt.pgStatTabHash)
return NULL;
PgStat_StatTabKey rel_key;
rel_key.tableid = rel_id;
rel_key.statFlag = statFlag;
hash_entry = (TabStatHashEntry*)hash_search(u_sess->stat_cxt.pgStatTabHash, &rel_key, HASH_FIND, NULL);
if (!hash_entry)
return NULL;
return hash_entry->tsa_entry;
}
* get_tabstat_stack_level - add a new (sub)transaction stack entry if needed
*/
static PgStat_SubXactStatus* get_tabstat_stack_level(int nest_level)
{
PgStat_SubXactStatus* xact_state = NULL;
xact_state = u_sess->stat_cxt.pgStatXactStack;
if (xact_state == NULL || xact_state->nest_level != nest_level) {
xact_state =
(PgStat_SubXactStatus*)MemoryContextAlloc(u_sess->top_transaction_mem_cxt, sizeof(PgStat_SubXactStatus));
xact_state->nest_level = nest_level;
xact_state->prev = u_sess->stat_cxt.pgStatXactStack;
xact_state->first = NULL;
u_sess->stat_cxt.pgStatXactStack = xact_state;
}
return xact_state;
}
* 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 = NULL;
PgStat_TableXactStatus* trans = NULL;
* 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 = get_tabstat_stack_level(nest_level);
trans = (PgStat_TableXactStatus*)MemoryContextAllocZero(
u_sess->top_transaction_mem_cxt, 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;
}
* pgstat_count_heap_insert - count a tuple insertion of n tuples
*/
void pgstat_count_heap_insert(Relation rel, int n)
{
PgStat_TableStatus* pgstat_info = rel->pgstat_info;
if (pgstat_info != NULL) {
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_info->trans->tuples_inserted += n;
pgstat_info->trans->tuples_inserted_accum += n;
}
}
* pgstat_count_heap_update - count a tuple update
*/
void pgstat_count_heap_update(Relation rel, bool hot)
{
PgStat_TableStatus* pgstat_info = rel->pgstat_info;
if (pgstat_info != NULL) {
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_info->trans->tuples_updated++;
pgstat_info->trans->tuples_updated_accum++;
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)
{
PgStat_TableStatus* pgstat_info = rel->pgstat_info;
if (pgstat_info != NULL) {
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_info->trans->tuples_deleted++;
pgstat_info->trans->tuples_deleted_accum++;
}
}
* pgstat_truncate_save_counters
*
* Whenever a table is truncated, we save its i/u/d counters so that they can
* be cleared, and if the (sub)xact that executed the truncate later aborts,
* the counters can be restored to the saved (pre-truncate) values. Note we do
* this on the first truncate in any particular subxact level only.
*/
static void pgstat_truncate_save_counters(PgStat_TableXactStatus* trans)
{
if (!trans->truncated) {
trans->inserted_pre_trunc = trans->tuples_inserted;
trans->updated_pre_trunc = trans->tuples_updated;
trans->deleted_pre_trunc = trans->tuples_deleted;
trans->inplace_updated_pre_trunc = trans->tuples_inplace_updated;
trans->truncated = true;
}
}
* pgstat_truncate_restore_counters - restore counters when a truncate aborts
*/
static void pgstat_truncate_restore_counters(PgStat_TableXactStatus* trans)
{
if (trans->truncated) {
trans->tuples_inserted = trans->inserted_pre_trunc;
trans->tuples_updated = trans->updated_pre_trunc;
trans->tuples_deleted = trans->deleted_pre_trunc;
trans->tuples_inplace_updated = trans->inplace_updated_pre_trunc;
}
}
* pgstat_count_truncate - update tuple counters due to truncate
*/
void pgstat_count_truncate(Relation rel)
{
PgStat_TableStatus* pgstat_info = rel->pgstat_info;
if (pgstat_info != NULL) {
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_truncate_save_counters(pgstat_info->trans);
pgstat_info->trans->tuples_inserted = 0;
pgstat_info->trans->tuples_updated = 0;
pgstat_info->trans->tuples_deleted = 0;
pgstat_info->trans->tuples_inplace_updated = 0;
}
}
* pgstat_count_uheap_update - count a tuple update inplace
*/
void
PgstatCountHeapUpdateInplace(Relation rel)
{
PgStat_TableStatus *pgstat_info = rel->pgstat_info;
if (pgstat_info != NULL) {
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_info->trans->tuples_updated++;
pgstat_info->trans->tuples_updated_accum++;
pgstat_info->trans->tuples_inplace_updated++;
}
}
* 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)
{
PgStat_TableStatus* pgstat_info = rel->pgstat_info;
if (pgstat_info != NULL)
pgstat_info->t_counts.t_delta_dead_tuples -= delta;
}
* pgstat_count_cu/dfs_update - count a cstore/dfs update
*/
void pgstat_count_cu_update(Relation rel, int n)
{
PgStat_TableStatus* pgstat_info = rel->pgstat_info;
if (pgstat_info != NULL) {
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_info->trans->tuples_updated += n;
pgstat_info->trans->tuples_updated_accum += n;
}
}
* pgstat_count_cu/dfs_delete - count a cstore/dfs deletion
*/
void pgstat_count_cu_delete(Relation rel, int n)
{
PgStat_TableStatus* pgstat_info = rel->pgstat_info;
if (pgstat_info != NULL) {
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_info->trans->tuples_deleted += n;
pgstat_info->trans->tuples_deleted_accum += n;
}
}
static inline void init_tuplecount_truncate(PgStat_TableStatus* tabstat)
{
tabstat->t_counts.t_tuples_inserted_post_truncate = 0;
tabstat->t_counts.t_tuples_updated_post_truncate = 0;
tabstat->t_counts.t_tuples_deleted_post_truncate = 0;
tabstat->t_counts.t_tuples_inplace_updated_post_truncate = 0;
tabstat->t_counts.t_delta_live_tuples = 0;
tabstat->t_counts.t_delta_dead_tuples = 0;
}
* AtEOXact_PgStat
*
* Called from access/transam/xact.c at top-level transaction commit/abort.
* ----------
*/
void AtEOXact_PgStat(bool isCommit)
{
PgStat_SubXactStatus* xact_state = NULL;
* Count transaction commit or abort. (We use counters, not just bools,
* in case the reporting message isn't sent right away.)
*/
if (isCommit) {
u_sess->stat_cxt.pgStatXactCommit++;
} else if (!AM_WAL_DB_SENDER) {
* Walsender for logical decoding will not count rollback transaction
* any more. Logical decoding frequently starts and rollback transactions
* to access system tables and syscache, see ReorderBufferCommit()
* for more details.
*/
u_sess->stat_cxt.pgStatXactRollback++;
}
pgstatCountTransactionCommit4SessionLevel(isCommit);
* 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 u_sess->top_transaction_mem_cxt and will go away anyway.
*/
xact_state = u_sess->stat_cxt.pgStatXactStack;
if (xact_state != NULL) {
PgStat_TableXactStatus* trans = NULL;
Assert(xact_state->nest_level == 1);
Assert(xact_state->prev == NULL);
for (trans = xact_state->first; trans != NULL; trans = trans->next) {
PgStat_TableStatus* tabstat = NULL;
Assert(trans->nest_level == 1);
Assert(trans->upper == NULL);
tabstat = trans->parent;
Assert(tabstat->trans == trans);
if (!isCommit)
pgstat_truncate_restore_counters(trans);
tabstat->t_counts.t_tuples_inserted += trans->tuples_inserted_accum;
tabstat->t_counts.t_tuples_updated += trans->tuples_updated_accum;
tabstat->t_counts.t_tuples_deleted += trans->tuples_deleted_accum;
tabstat->t_counts.t_tuples_inplace_updated += trans->tuples_inplace_updated_accum;
if (isCommit) {
tabstat->t_counts.t_truncated = trans->truncated;
if (trans->truncated) {
init_tuplecount_truncate(tabstat);
}
if (tabstat->t_counts.t_truncated) {
tabstat->t_counts.t_tuples_inserted_post_truncate += trans->tuples_inserted;
tabstat->t_counts.t_tuples_updated_post_truncate += trans->tuples_updated;
tabstat->t_counts.t_tuples_deleted_post_truncate += trans->tuples_deleted;
tabstat->t_counts.t_tuples_inplace_updated_post_truncate += trans->tuples_inplace_updated;
}
tabstat->t_counts.t_delta_live_tuples += trans->tuples_inserted - trans->tuples_deleted;
tabstat->t_counts.t_delta_dead_tuples +=
(trans->tuples_updated - trans->tuples_inplace_updated) + trans->tuples_deleted;
tabstat->t_counts.t_changed_tuples +=
trans->tuples_inserted + trans->tuples_updated + trans->tuples_deleted;
} else {
tabstat->t_counts.t_delta_dead_tuples +=
trans->tuples_inserted + (trans->tuples_updated - trans->tuples_inplace_updated);
}
tabstat->trans = NULL;
}
}
u_sess->stat_cxt.pgStatXactStack = NULL;
pgstat_clear_snapshot();
}
* AtEOSubXact_PgStat
*
* Called from access/transam/xact.c at subtransaction commit/abort.
* ----------
*/
void AtEOSubXact_PgStat(bool isCommit, int nestDepth)
{
PgStat_SubXactStatus* xact_state = NULL;
* Transfer transactional insert/update counts into the next higher
* subtransaction state.
*/
xact_state = u_sess->stat_cxt.pgStatXactStack;
if (xact_state != NULL && xact_state->nest_level >= nestDepth) {
PgStat_TableXactStatus* trans = NULL;
PgStat_TableXactStatus* next_trans = NULL;
u_sess->stat_cxt.pgStatXactStack = xact_state->prev;
for (trans = xact_state->first; trans != NULL; trans = next_trans) {
PgStat_TableStatus* tabstat = NULL;
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->truncated) {
pgstat_truncate_save_counters(trans->upper);
trans->upper->tuples_inserted = trans->tuples_inserted;
trans->upper->tuples_updated = trans->tuples_updated;
trans->upper->tuples_deleted = trans->tuples_deleted;
trans->upper->tuples_inplace_updated = trans->tuples_inplace_updated;
} else {
trans->upper->tuples_inserted += trans->tuples_inserted;
trans->upper->tuples_updated += trans->tuples_updated;
trans->upper->tuples_deleted += trans->tuples_deleted;
trans->upper->tuples_inplace_updated += trans->tuples_inplace_updated;
}
trans->upper->tuples_inserted_accum += trans->tuples_inserted_accum;
trans->upper->tuples_updated_accum += trans->tuples_updated_accum;
trans->upper->tuples_deleted_accum += trans->tuples_deleted_accum;
trans->upper->tuples_inplace_updated_accum += trans->tuples_inplace_updated_accum;
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
* u_sess->top_transaction_mem_cxt anyway). We have to re-link it
* into the parent level, though, and that might mean
* pushing a new entry into the u_sess->stat_cxt.pgStatXactStack.
*/
PgStat_SubXactStatus* upper_xact_state = NULL;
upper_xact_state = get_tabstat_stack_level(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
*/
pgstat_truncate_restore_counters(trans);
tabstat->t_counts.t_tuples_inserted += trans->tuples_inserted_accum;
tabstat->t_counts.t_tuples_updated += trans->tuples_updated_accum;
tabstat->t_counts.t_tuples_deleted += trans->tuples_deleted_accum;
tabstat->t_counts.t_tuples_inplace_updated += trans->tuples_inplace_updated_accum;
tabstat->t_counts.t_delta_dead_tuples +=
trans->tuples_inserted + (trans->tuples_updated - trans->tuples_inplace_updated);
tabstat->trans = trans->upper;
pfree(trans);
}
}
pfree(xact_state);
}
}
* AtPrepare_PgStat
* Save the transactional stats state at 2PC transaction prepare.
*
* In this phase we just generate 2PC records for all the pending
* transaction-dependent stats work.
*/
void AtPrepare_PgStat(void)
{
PgStat_SubXactStatus* xact_state = NULL;
xact_state = u_sess->stat_cxt.pgStatXactStack;
if (xact_state != NULL) {
PgStat_TableXactStatus* trans = NULL;
Assert(xact_state->nest_level == 1);
Assert(xact_state->prev == NULL);
for (trans = xact_state->first; trans != NULL; trans = trans->next) {
PgStat_TableStatus* tabstat = NULL;
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.tuples_inplace_updated = trans->tuples_inplace_updated;
record.tuples_inserted_accum = trans->tuples_inserted_accum;
record.tuples_updated_accum = trans->tuples_updated_accum;
record.tuples_deleted_accum = trans->tuples_deleted_accum;
record.tuples_inplace_updated_accum = trans->tuples_inplace_updated_accum;
record.inserted_pre_trunc = trans->inserted_pre_trunc;
record.updated_pre_trunc = trans->updated_pre_trunc;
record.deleted_pre_trunc = trans->deleted_pre_trunc;
record.inplace_updated_pre_trunc = trans->inplace_updated_pre_trunc;
record.t_id = tabstat->t_id;
record.t_shared = tabstat->t_shared;
record.t_statFlag = tabstat->t_statFlag;
record.t_truncated = trans->truncated;
RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0, &record, sizeof(TwoPhasePgStatRecord));
}
}
}
* PostPrepare_PgStat
* Clean up after successful PREPARE.
*
* 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 is not called during PREPARE.
*/
void PostPrepare_PgStat(void)
{
PgStat_SubXactStatus* xact_state = NULL;
* We don't bother to free any of the transactional state, since it's all
* in u_sess->top_transaction_mem_cxt and will go away anyway.
*/
xact_state = u_sess->stat_cxt.pgStatXactStack;
if (xact_state != NULL) {
PgStat_TableXactStatus* trans = NULL;
for (trans = xact_state->first; trans != NULL; trans = trans->next) {
PgStat_TableStatus* tabstat = NULL;
tabstat = trans->parent;
tabstat->trans = NULL;
}
}
u_sess->stat_cxt.pgStatXactStack = NULL;
pgstat_clear_snapshot();
}
* 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 = NULL;
pgstat_info = get_tabstat_entry(rec->t_id, rec->t_shared, rec->t_statFlag);
pgstat_info->t_counts.t_tuples_inserted += rec->tuples_inserted_accum;
pgstat_info->t_counts.t_tuples_updated += rec->tuples_updated_accum;
pgstat_info->t_counts.t_tuples_deleted += rec->tuples_deleted_accum;
pgstat_info->t_counts.t_tuples_inplace_updated += rec->tuples_inplace_updated_accum;
pgstat_info->t_counts.t_truncated = rec->t_truncated;
if (rec->t_truncated) {
init_tuplecount_truncate(pgstat_info);
}
if (pgstat_info->t_counts.t_truncated) {
pgstat_info->t_counts.t_tuples_inserted_post_truncate += rec->tuples_inserted;
pgstat_info->t_counts.t_tuples_updated_post_truncate += rec->tuples_updated;
pgstat_info->t_counts.t_tuples_deleted_post_truncate += rec->tuples_deleted;
pgstat_info->t_counts.t_tuples_inplace_updated_post_truncate += rec->tuples_inplace_updated;
}
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_inplace_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 = NULL;
pgstat_info = get_tabstat_entry(rec->t_id, rec->t_shared, rec->t_statFlag);
if (rec->t_truncated) {
rec->tuples_inserted = rec->inserted_pre_trunc;
rec->tuples_updated = rec->updated_pre_trunc;
rec->tuples_deleted = rec->deleted_pre_trunc;
rec->tuples_inplace_updated = rec->inplace_updated_pre_trunc;
}
pgstat_info->t_counts.t_tuples_inserted += rec->tuples_inserted_accum;
pgstat_info->t_counts.t_tuples_updated += rec->tuples_updated_accum;
pgstat_info->t_counts.t_tuples_deleted += rec->tuples_deleted_accum;
pgstat_info->t_counts.t_tuples_inplace_updated += rec->tuples_inplace_updated_accum;
pgstat_info->t_counts.t_delta_dead_tuples +=
rec->tuples_inserted + (rec->tuples_updated - rec->tuples_inplace_updated);
}
* 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)
{
* If not done for this transaction, read the statistics collector stats
* file into some hash tables.
*/
backend_read_statsfile();
* Lookup the requested database; return NULL if not found
*/
return (PgStat_StatDBEntry*)hash_search(u_sess->stat_cxt.pgStatDBHash, (void*)&dbid, HASH_FIND, NULL);
}
* 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(PgStat_StatTabKey* tabkey)
{
Oid dbid;
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatTabEntry* tabentry = NULL;
* If not done for this transaction, read the statistics collector stats
* file into some hash tables.
*/
backend_read_statsfile();
* Lookup our database, then look in its table hash table.
*/
dbid = u_sess->proc_cxt.MyDatabaseId;
dbentry = (PgStat_StatDBEntry*)hash_search(u_sess->stat_cxt.pgStatDBHash, (void*)&dbid, HASH_FIND, NULL);
if (dbentry != NULL && dbentry->tables != NULL) {
tabentry = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)tabkey, HASH_FIND, NULL);
if (tabentry != NULL)
return tabentry;
}
* If we didn't find it, maybe it's a shared table.
*/
dbid = InvalidOid;
dbentry = (PgStat_StatDBEntry*)hash_search(u_sess->stat_cxt.pgStatDBHash, (void*)&dbid, HASH_FIND, NULL);
if (dbentry != NULL && dbentry->tables != NULL) {
tabentry = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)tabkey, HASH_FIND, NULL);
if (tabentry != NULL)
return tabentry;
}
return NULL;
}
* pgstat_fetch_stat_funcentry() -
*
* Support function for the SQL-callable pgstat* functions. Returns
* the collected statistics for one function or NULL.
* ----------
*/
PgStat_StatFuncEntry* pgstat_fetch_stat_funcentry(Oid func_id)
{
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatFuncEntry* funcentry = NULL;
backend_read_statsfile();
dbentry = pgstat_fetch_stat_dbentry(u_sess->proc_cxt.MyDatabaseId);
if (dbentry != NULL && dbentry->functions != NULL) {
funcentry = (PgStat_StatFuncEntry*)hash_search(dbentry->functions, (void*)&func_id, HASH_FIND, NULL);
}
return funcentry;
}
* ---------
* pgstat_fetch_global() -
*
* Support function for the SQL-callable pgstat* functions. Returns
* a pointer to the global statistics struct.
* ---------
*/
PgStat_GlobalStats* pgstat_fetch_global(void)
{
backend_read_statsfile();
return u_sess->stat_cxt.globalStats;
}
* Functions for management of the shared-memory PgBackendStatus array
* ------------------------------------------------------------
*/
static THR_LOCAL char* BackendNspRelnameBuffer = NULL;
PgBackendStatus* PgBackendStatusArray = NULL;
THR_LOCAL XLogStatCollect *g_xlog_stat_shared = NULL;
Size XLogStatShmemSize(void)
{
return sizeof(XLogStatCollect);
}
void XLogStatShmemInit(void)
{
bool found = false;
errno_t rc;
g_xlog_stat_shared = (XLogStatCollect *)ShmemInitStruct("XLogStat", XLogStatShmemSize(), &found);
g_xlog_stat_shared->remoteFlushWaitCount = 0;
if (!IsUnderPostmaster) {
Assert(!found);
rc = memset_s(g_xlog_stat_shared, XLogStatShmemSize(), 0, XLogStatShmemSize());
securec_check(rc, "\0", "\0");
} else {
Assert(found);
}
}
* ---------
* pgstat_fetch_waitcount() -
*
* Support function for the SQL-callable pgstat* functions. Returns
* a pointer to WaitCountBuffer.
* ---------
*/
PgStat_WaitCountStatus* pgstat_fetch_waitcount(void)
{
return t_thrd.shemem_ptr_cxt.WaitCountBuffer;
}
* Report shared-memory space needed by CreateSharedBackendStatus.
*/
Size BackendStatusShmemSize(void)
{
Size size;
size = mul_size(sizeof(PgBackendStatus), BackendStatusArray_size);
size = add_size(size, mul_size(NAMEDATALEN, BackendStatusArray_size));
size = add_size(size, mul_size(CONNECTIONINFO_LEN, BackendStatusArray_size));
size = add_size(size, mul_size(NAMEDATALEN, BackendStatusArray_size));
size =
add_size(size, mul_size(g_instance.attr.attr_common.pgstat_track_activity_query_size, BackendStatusArray_size));
size = add_size(size, sizeof(PgStat_WaitCountStatus));
size = add_size(size, mul_size(NAMEDATALEN * 2, BackendStatusArray_size));
return size;
}
* init memory whose size may be large than INT_MAX by calling func memset_s many times
*/
static void MemsetHugeSize(char* dest, const Size size, const char c)
{
Size block_size = 0x40000000;
errno_t rc;
Size remain_size = size;
char* ptr = dest;
while (remain_size > 0) {
if (remain_size < block_size) {
block_size = remain_size;
}
rc = memset_s(ptr, block_size, c, block_size);
securec_check(rc, "\0", "\0");
ptr += block_size;
remain_size -= block_size;
}
}
* Initialize the shared status array and several string buffers
* during postmaster startup.
*/
void CreateSharedBackendStatus(void)
{
Size size;
bool found = false;
int i;
char* buffer = NULL;
errno_t rc;
size = mul_size(sizeof(PgBackendStatus), BackendStatusArray_size);
t_thrd.shemem_ptr_cxt.BackendStatusArray = (PgBackendStatus*)ShmemInitStruct("Backend Status Array", size, &found);
if (!found) {
* We're the first - initialize.
* call MemsetHugeSize because the size may be larger than INT_MAX,
* when memset_s can only init memory less than INT_MAX.
*/
MemsetHugeSize((char*)t_thrd.shemem_ptr_cxt.BackendStatusArray, size, 0);
TimestampTz current_time = GetCurrentTimestamp();
for (i = 0; i < BackendStatusArray_size; i++) {
(void)syscalllockInit(&t_thrd.shemem_ptr_cxt.BackendStatusArray[i].statement_cxt_lock);
InstrWaitEventInitLastUpdated(&t_thrd.shemem_ptr_cxt.BackendStatusArray[i], current_time);
}
}
size = mul_size(NAMEDATALEN, BackendStatusArray_size);
t_thrd.shemem_ptr_cxt.BackendAppnameBuffer =
(char*)ShmemInitStruct("Backend Application Name Buffer", size, &found);
if (!found) {
rc = memset_s(t_thrd.shemem_ptr_cxt.BackendAppnameBuffer, size, 0, size);
securec_check(rc, "\0", "\0");
buffer = t_thrd.shemem_ptr_cxt.BackendAppnameBuffer;
for (i = 0; i < BackendStatusArray_size; i++) {
t_thrd.shemem_ptr_cxt.BackendStatusArray[i].st_appname = buffer;
buffer += NAMEDATALEN;
}
}
size = mul_size(CONNECTIONINFO_LEN, BackendStatusArray_size);
t_thrd.shemem_ptr_cxt.BackendConninfoBuffer =
(char*)ShmemInitStruct("Backend Connection Information Buffer", size, &found);
if (!found) {
* when memset_s can only init memory less than INT_MAX.
*/
MemsetHugeSize((char*)t_thrd.shemem_ptr_cxt.BackendConninfoBuffer, size, 0);
buffer = t_thrd.shemem_ptr_cxt.BackendConninfoBuffer;
for (i = 0; i < BackendStatusArray_size; i++) {
t_thrd.shemem_ptr_cxt.BackendStatusArray[i].st_conninfo = buffer;
buffer += CONNECTIONINFO_LEN;
}
}
size = mul_size(NAMEDATALEN, BackendStatusArray_size);
t_thrd.shemem_ptr_cxt.BackendClientHostnameBuffer =
(char*)ShmemInitStruct("Backend Client Host Name Buffer", size, &found);
if (!found) {
rc = memset_s(t_thrd.shemem_ptr_cxt.BackendClientHostnameBuffer, size, 0, size);
securec_check(rc, "\0", "\0");
buffer = t_thrd.shemem_ptr_cxt.BackendClientHostnameBuffer;
for (i = 0; i < BackendStatusArray_size; i++) {
t_thrd.shemem_ptr_cxt.BackendStatusArray[i].st_clienthostname = buffer;
buffer += NAMEDATALEN;
}
}
t_thrd.shemem_ptr_cxt.BackendActivityBufferSize =
mul_size(g_instance.attr.attr_common.pgstat_track_activity_query_size, BackendStatusArray_size);
t_thrd.shemem_ptr_cxt.BackendActivityBuffer =
(char*)ShmemInitStruct("Backend Activity Buffer", t_thrd.shemem_ptr_cxt.BackendActivityBufferSize, &found);
if (!found) {
MemsetHugeSize(
t_thrd.shemem_ptr_cxt.BackendActivityBuffer, t_thrd.shemem_ptr_cxt.BackendActivityBufferSize, 0);
buffer = t_thrd.shemem_ptr_cxt.BackendActivityBuffer;
for (i = 0; i < BackendStatusArray_size; i++) {
t_thrd.shemem_ptr_cxt.BackendStatusArray[i].st_activity = buffer;
buffer += g_instance.attr.attr_common.pgstat_track_activity_query_size;
}
}
size = sizeof(PgStat_WaitCountStatus);
t_thrd.shemem_ptr_cxt.WaitCountBuffer = (PgStat_WaitCountStatus*)ShmemInitStruct("Wait Count Buffer", size, &found);
if (!found) {
rc = memset_s(t_thrd.shemem_ptr_cxt.WaitCountBuffer, size, 0, size);
securec_check(rc, "\0", "\0");
}
size = mul_size(NAMEDATALEN * 2, BackendStatusArray_size);
BackendNspRelnameBuffer = (char*)ShmemInitStruct("Backend Namespace Relname Buffer", size, &found);
if (!found) {
rc = memset_s(BackendNspRelnameBuffer, size, 0, size);
securec_check(rc, "\0", "\0");
buffer = BackendNspRelnameBuffer;
for (i = 0; i < BackendStatusArray_size; i++) {
t_thrd.shemem_ptr_cxt.BackendStatusArray[i].st_relname = buffer;
buffer += NAMEDATALEN * 2;
}
}
if (PgBackendStatusArray == NULL)
PgBackendStatusArray = t_thrd.shemem_ptr_cxt.BackendStatusArray;
}
static int GetAuxProcStatEntryIndex()
{
int index = GetAuxProcEntryIndex(MAX_BACKEND_SLOT);
if (t_thrd.bootstrap_cxt.MyAuxProcType == PageRedoProcess) {
SetPageRedoWorkerIndex(index);
}
return index;
}
static const char* REMOTE_CONN_TYPET[REMOTE_CONN_GTM_TOOL + 1] = {
"app", "coordinator", "datanode", "gtm", "gtm_proxy", "inetrnal_tool", "gtm_tool"};
const char* remote_conn_type_string(int remote_conn_type)
{
Assert(remote_conn_type >= REMOTE_CONN_APP && remote_conn_type <= REMOTE_CONN_GTM_TOOL);
return REMOTE_CONN_TYPET[remote_conn_type];
}
* pgstat_initialize() -
*
* Initialize pgstats state, and set up our on-proc-exit hook.
* Called from InitPostgres. t_thrd.proc_cxt.MyBackendId must be set,
* but we must not have started any transaction yet (since the
* exit hook must run after the last transaction exit).
* NOTE: u_sess->proc_cxt.MyDatabaseId isn't set yet; so the shutdown hook has to be careful.
* ----------
*/
void pgstat_initialize(void)
{
Assert(t_thrd.shemem_ptr_cxt.BackendStatusArray);
if (t_thrd.proc_cxt.MyBackendId != InvalidBackendId) {
Assert(t_thrd.proc_cxt.MyBackendId >= 1 &&
t_thrd.proc_cxt.MyBackendId <= (g_instance.attr.attr_common.enable_thread_pool
? GLOBAL_RESERVE_SESSION_NUM
: g_instance.shmem_cxt.MaxBackends));
t_thrd.shemem_ptr_cxt.MyBEEntry = &t_thrd.shemem_ptr_cxt.BackendStatusArray[t_thrd.proc_cxt.MyBackendId - 1];
} else {
int index = GetAuxProcStatEntryIndex();
t_thrd.shemem_ptr_cxt.MyBEEntry = &t_thrd.shemem_ptr_cxt.BackendStatusArray[index];
}
initLocalBadBlockStat();
initMySessionStatEntry();
initMySessionTimeEntry();
if (t_thrd.role != STREAM_WORKER)
initMySessionMemoryEntry();
on_shmem_exit(pgstat_beshutdown_hook, 0);
}
* pgstat_initialize_session() -
*
* change MyBEEntry to session entry
*/
void pgstat_initialize_session(void)
{
Assert(t_thrd.shemem_ptr_cxt.BackendStatusArray);
Assert(u_sess->session_ctr_index >= GLOBAL_RESERVE_SESSION_NUM && u_sess->session_ctr_index < MAX_BACKEND_SLOT);
Assert(t_thrd.proc_cxt.MyBackendId != InvalidBackendId);
Assert(
t_thrd.shemem_ptr_cxt.MyBEEntry == &t_thrd.shemem_ptr_cxt.BackendStatusArray[t_thrd.proc_cxt.MyBackendId - 1]);
* pool worker reports being coupled to one session
* app name of thread pool worker might be over-written during session
* guc initilization, need to restore it.
*/
pgstat_report_appname("ThreadPoolWorker");
pgstat_report_activity(STATE_COUPLED, NULL);
t_thrd.shemem_ptr_cxt.MyBEEntry = &t_thrd.shemem_ptr_cxt.BackendStatusArray[u_sess->session_ctr_index];
AttachMySessionStatEntry();
AttachMySessionTimeEntry();
AttachMySessionMemoryEntry();
}
* pgstat_deinitialize_session() -
*
* change MyBEEntry back to thread pool worker entry
*/
void pgstat_deinitialize_session(void)
{
Assert(t_thrd.shemem_ptr_cxt.BackendStatusArray);
Assert((u_sess->session_ctr_index >= GLOBAL_RESERVE_SESSION_NUM && u_sess->session_ctr_index < MAX_BACKEND_SLOT) ||
u_sess == t_thrd.fake_session);
Assert(t_thrd.proc_cxt.MyBackendId != InvalidBackendId);
t_thrd.shemem_ptr_cxt.mySessionStatEntry = &t_thrd.shemem_ptr_cxt.sessionStatArray[t_thrd.proc_cxt.MyBackendId - 1];
t_thrd.shemem_ptr_cxt.mySessionTimeEntry = &t_thrd.shemem_ptr_cxt.sessionTimeArray[t_thrd.proc_cxt.MyBackendId - 1];
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry =
&t_thrd.shemem_ptr_cxt.sessionMemoryArray[t_thrd.proc_cxt.MyBackendId - 1];
if (!t_thrd.proc_cxt.proc_exit_inprogress) {
t_thrd.shemem_ptr_cxt.MyBEEntry = &t_thrd.shemem_ptr_cxt.BackendStatusArray[t_thrd.proc_cxt.MyBackendId - 1];
* reset worker's pgproc memory info
* be careful that during process exit, t_thrd.proc may have been cleared
*/
t_thrd.proc->sessMemorySessionid = t_thrd.fake_session->session_id;
pgstat_report_activity(STATE_DECOUPLED, NULL);
}
}
* pgstat_bestart() -
*
* Initialize this backend's entry in the PgBackendStatus array.
* Called from InitPostgres.
* u_sess->proc_cxt.MyDatabaseId, session userid, and application_name must be set
* (hence, this cannot be combined with pgstat_initialize).
* ----------
*/
void pgstat_bestart(void)
{
TimestampTz proc_start_timestamp;
Oid userid = InvalidOid;
SockAddr clientaddr;
volatile PgBackendStatus* beentry = NULL;
errno_t rc = 0;
* To minimize the time spent modifying the PgBackendStatus entry, fetch
* all the needed data first.
*
* If we have a u_sess->proc_cxt.MyProcPort, use its session start time (for consistency,
* and to save a kernel call).
*/
if (u_sess->proc_cxt.MyProcPort) {
proc_start_timestamp = u_sess->proc_cxt.MyProcPort->SessionStartTime;
} else if (t_thrd.proc_cxt.MyStartTime) {
proc_start_timestamp = t_thrd.proc_cxt.MyStartTime;
} else {
proc_start_timestamp = GetCurrentTimestamp();
}
if (t_thrd.proc_cxt.MyBackendId != InvalidBackendId && !IsCatchupProcess() &&
!(IS_THREAD_POOL_WORKER && u_sess->session_id == 0)) {
userid = GetSessionUserId();
}
* We may not have a u_sess->proc_cxt.MyProcPort (eg, if this is the autovacuum process).
* If so, use all-zeroes client address, which is dealt with specially in
* pg_stat_get_backend_client_addr and pg_stat_get_backend_client_port.
*/
if (u_sess->proc_cxt.MyProcPort)
rc = memcpy_s(&clientaddr, sizeof(clientaddr), &u_sess->proc_cxt.MyProcPort->raddr, sizeof(clientaddr));
else
rc = memset_s(&clientaddr, sizeof(clientaddr), 0, sizeof(clientaddr));
securec_check(rc, "\0", "\0");
* Initialize my status entry, following the protocol of bumping
* st_changecount before and after; and make sure it's even afterwards. We
* use a volatile pointer here to ensure the compiler doesn't try to get
* cute.
*/
beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
do {
pgstat_increment_changecount_before(beentry);
} while ((beentry->st_changecount & 1) == 0);
beentry->st_procpid = t_thrd.proc_cxt.MyProcPid;
if (!ENABLE_THREAD_POOL) {
beentry->st_sessionid = t_thrd.proc_cxt.MyProcPid;
} else {
if (IS_THREAD_POOL_WORKER && u_sess->session_id > 0)
beentry->st_sessionid = u_sess->session_id;
else
beentry->st_sessionid = t_thrd.proc_cxt.MyProcPid;
}
beentry->globalSessionId.sessionId = 0;
beentry->globalSessionId.nodeId = 0;
beentry->globalSessionId.seq = 0;
beentry->st_proc_start_timestamp = proc_start_timestamp;
beentry->st_activity_start_timestamp = 0;
beentry->st_state_start_timestamp = 0;
beentry->st_xact_start_timestamp = 0;
beentry->st_databaseid = u_sess->proc_cxt.MyDatabaseId;
beentry->st_userid = userid;
rc = memcpy_s((void*)&beentry->st_clientaddr, sizeof(SockAddr), &clientaddr, sizeof(clientaddr));
securec_check(rc, "\0", "\0");
beentry->st_clienthostname[0] = '\0';
beentry->st_state = STATE_UNDEFINED;
beentry->st_appname[0] = '\0';
beentry->st_conninfo[0] = '\0';
beentry->st_activity[0] = '\0';
beentry->st_clienthostname[NAMEDATALEN - 1] = '\0';
beentry->st_appname[NAMEDATALEN - 1] = '\0';
beentry->st_conninfo[CONNECTIONINFO_LEN - 1] = '\0';
beentry->st_activity[g_instance.attr.attr_common.pgstat_track_activity_query_size - 1] = '\0';
beentry->st_queryid = 0;
beentry->st_unique_sql_key.unique_sql_id = 0;
beentry->st_unique_sql_key.user_id = 0;
beentry->st_unique_sql_key.cn_id = 0;
beentry->st_tid = gettid();
beentry->st_parent_sessionid = 0;
beentry->st_thread_level = 0;
beentry->st_smpid = 0;
beentry->st_waitstatus = STATE_WAIT_UNDEFINED;
beentry->st_nodeid = -1;
beentry->st_waitnode_count = 0;
beentry->st_plannodeid = -1;
beentry->st_numnodes = -1;
beentry->trace_cxt.trace_id[0] = '\0';
beentry->st_waitevent = WAIT_EVENT_END;
beentry->st_xid = 0;
beentry->st_waitstatus_phase = PHASE_NONE;
beentry->st_relname[0] = '\0';
beentry->st_relname[NAMEDATALEN * 2 - 1] = '\0';
beentry->st_libpq_wait_nodeid = InvalidOid;
beentry->st_libpq_wait_nodecount = 0;
beentry->st_tempid = 0;
beentry->st_timelineid = 0;
beentry->st_debug_info = &u_sess->wlm_cxt->wlm_debug_info;
beentry->st_cgname = u_sess->wlm_cxt->control_group;
beentry->st_stmtmem = 0;
beentry->st_block_sessionid = 0;
beentry->st_connect_info = u_sess->pgxc_cxt.PoolerConnectionInfo;
* st_gtmhost and st_gtmtimeline fields are not initialized here,
* because they have already been registered in InitPostgres
* with StartTransactionCommand.
*/
do {
beentry->lw_count++;
} while (CHANGECOUNT_IS_EVEN(beentry->lw_count));
beentry->lw_want_lock = NULL;
beentry->lw_want_mode = LW_SHARED;
beentry->lw_want_start_time = (TimestampTz)0;
beentry->lw_held_num = get_held_lwlocks_num();
beentry->lw_held_locks = get_held_lwlocks();
beentry->lw_held_times = get_lwlock_held_times();
beentry->st_lw_access_flag = false;
beentry->st_lw_is_cleanning_flag = false;
pgstat_increment_changecount_after(beentry);
beentry->statement_cxt = bind_statement_context();
if (u_sess->proc_cxt.MyProcPort != NULL) {
if (u_sess->proc_cxt.MyProcPort->is_logic_conn) {
if (u_sess->proc_cxt.MyProcPort->libcomm_addrinfo != NULL) {
rc = memcpy_s((void*)&beentry->remote_info.remote_name,
NAMEDATALEN,
u_sess->proc_cxt.MyProcPort->libcomm_addrinfo->nodename,
NAMEDATALEN);
securec_check(rc, "\0", "\0");
rc = memcpy_s((void*)&beentry->remote_info.remote_ip,
MAX_IP_STR_LEN,
u_sess->proc_cxt.MyProcPort->libcomm_addrinfo->host,
strlen(u_sess->proc_cxt.MyProcPort->libcomm_addrinfo->host) + 1);
securec_check(rc, "\0", "\0");
rc = memcpy_s((void*)&beentry->remote_info.remote_port,
MAX_PORT_LEN,
&(u_sess->proc_cxt.MyProcPort->libcomm_addrinfo->listen_port),
MAX_PORT_LEN);
securec_check(rc, "\0", "\0");
beentry->remote_info.socket = -1;
beentry->remote_info.logic_id = u_sess->proc_cxt.MyProcPort->gs_sock.sid;
}
} else {
if (u_sess->proc_cxt.MyProcPort->sock >= 0) {
const char* remote_node = remote_conn_type_string(u_sess->attr.attr_common.remoteConnType);
rc = memcpy_s(
(void*)&beentry->remote_info.remote_name, NAMEDATALEN, remote_node, strlen(remote_node) + 1);
securec_check(rc, "\0", "\0");
if (u_sess->proc_cxt.MyProcPort->remote_host != NULL) {
rc = memcpy_s((void*)&beentry->remote_info.remote_ip,
MAX_IP_STR_LEN,
u_sess->proc_cxt.MyProcPort->remote_host,
strlen(u_sess->proc_cxt.MyProcPort->remote_host) + 1);
securec_check(rc, "\0", "\0");
}
if (u_sess->proc_cxt.MyProcPort->remote_port != NULL &&
u_sess->proc_cxt.MyProcPort->remote_port[0] != '\0') {
rc = memcpy_s((void*)&beentry->remote_info.remote_port,
MAX_PORT_LEN,
u_sess->proc_cxt.MyProcPort->remote_port,
strlen(u_sess->proc_cxt.MyProcPort->remote_port) + 1);
securec_check(rc, "\0", "\0");
}
beentry->remote_info.socket = u_sess->proc_cxt.MyProcPort->sock;
beentry->remote_info.logic_id = -1;
}
}
}
if (u_sess->proc_cxt.MyProcPort && u_sess->proc_cxt.MyProcPort->remote_hostname) {
strlcpy(beentry->st_clienthostname, u_sess->proc_cxt.MyProcPort->remote_hostname, NAMEDATALEN);
}
if (u_sess->attr.attr_common.application_name) {
pgstat_report_appname(u_sess->attr.attr_common.application_name);
}
if (u_sess->attr.attr_sql.connection_info) {
pgstat_report_conninfo(u_sess->attr.attr_sql.connection_info);
}
}
* only if light-weight lock information is never accessed by other backends,
* this backend can exit.
*/
static void WaitUntilLWLockInfoNeverAccess(volatile PgBackendStatus* backendEntry)
{
START_CRIT_SECTION();
backendEntry->st_lw_is_cleanning_flag = true;
pg_memory_barrier();
while (backendEntry->st_lw_access_flag) {
pg_usleep(1);
}
pg_memory_barrier();
backendEntry->lw_held_num = NULL;
backendEntry->lw_held_locks = NULL;
END_CRIT_SECTION();
}
* pgstat_couple_decouple_session() -
*
* update/remove thread info to coupled/decoupled session backend stat entry
* ----------
*/
void pgstat_couple_decouple_session(bool is_couple)
{
volatile PgBackendStatus* beentry = NULL;
Assert(u_sess->session_id != 0);
* update my status entry with new thread info, following the protocol of bumping
* st_changecount before and after; and make sure it's even afterwards. We
* use a volatile pointer here to ensure the compiler doesn't try to get
* cute.
*/
beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
do {
pgstat_increment_changecount_before(beentry);
} while ((beentry->st_changecount & 1) == 0);
Assert(beentry->st_sessionid == u_sess->session_id || beentry->st_sessionid == 0);
beentry->st_procpid = is_couple ? t_thrd.proc_cxt.MyProcPid : 0;
beentry->st_tid = is_couple ? gettid() : 0;
beentry->lw_held_num = is_couple ? get_held_lwlocks_num() : NULL;
beentry->lw_held_locks = is_couple ? get_held_lwlocks() : NULL;
beentry->lw_held_times = is_couple ? get_lwlock_held_times() : NULL;
do {
beentry->lw_count++;
} while (CHANGECOUNT_IS_EVEN(beentry->lw_count));
pgstat_increment_changecount_after(beentry);
}
static void clear_backend_entry(volatile PgBackendStatus* beentry)
{
* Clear status entry, following the protocol of bumping st_changecount
* before and after. We use a volatile pointer here to ensure the
* compiler doesn't try to get cute.
*/
pgstat_increment_changecount_before(beentry);
beentry->st_procpid = 0;
beentry->st_sessionid = 0;
beentry->globalSessionId.sessionId = 0;
beentry->globalSessionId.nodeId = 0;
beentry->globalSessionId.seq = 0;
beentry->my_prepared_queries = NULL;
beentry->my_pstmt_htbl_lock = NULL;
* make sure st_changecount is an even before release it.
*
* In case some thread was interrupted by SIGTERM at any time with a mess st_changecount
* in PgBackendStatus, PgstatCollectorMain may hang-up in waiting its change to even and
* can not exit after receiving SIGTERM signal
*/
do {
pgstat_increment_changecount_after(beentry);
} while ((beentry->st_changecount & 1) != 0);
}
* Shut down a single backend's statistics reporting at process exit.
*
* Flush any remaining statistics counts out to the collector.
* Without this, operations triggered during backend exit (such as
* temp table deletions) won't be counted.
*
* Lastly, clear out our entry in the PgBackendStatus array.
* For sessions in thread pool, just repoint stat object to pool worker.
*/
static void pgstat_beshutdown_hook(int code, Datum arg)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
* If we got as far as discovering our own database ID, we can report what
* we did to the collector. Otherwise, we'd be sending an invalid
* database ID, so forget it. (This means that accesses to pg_database
* during failed backend starts might never get counted.)
*/
* u_sess->proc_cxt.MyDatabaseId maybe non-zero only for backend threads
*/
if (OidIsValid(u_sess->proc_cxt.MyDatabaseId))
pgstat_report_stat(true);
clear_backend_entry(beentry);
* Thread pool worker also needs to clear
* t_thrd.shemem_ptr_cxt.BackendStatusArray[t_thrd.proc_cxt.MyBackendId - 1]
*/
if (IS_THREAD_POOL_WORKER && t_thrd.proc_cxt.MyBackendId != InvalidBackendId &&
beentry != &t_thrd.shemem_ptr_cxt.BackendStatusArray[t_thrd.proc_cxt.MyBackendId - 1]) {
clear_backend_entry(&t_thrd.shemem_ptr_cxt.BackendStatusArray[t_thrd.proc_cxt.MyBackendId - 1]);
WaitUntilLWLockInfoNeverAccess(&t_thrd.shemem_ptr_cxt.BackendStatusArray[t_thrd.proc_cxt.MyBackendId - 1]);
}
* handle below cases:
* if thread pool worker is shuting down, this hook is called previously,
* we need to release thread/session statement contexts.
* normal backend, only need to release thread statement context.
*/
if (IS_THREAD_POOL_WORKER && u_sess->session_ctr_index >= GLOBAL_RESERVE_SESSION_NUM &&
u_sess->session_ctr_index < MAX_BACKEND_SLOT) {
release_statement_context(&t_thrd.shemem_ptr_cxt.BackendStatusArray[u_sess->session_ctr_index],
__FUNCTION__, __LINE__);
}
if (t_thrd.proc_cxt.MyBackendId != InvalidBackendId) {
release_statement_context(&t_thrd.shemem_ptr_cxt.BackendStatusArray[t_thrd.proc_cxt.MyBackendId - 1],
__FUNCTION__, __LINE__);
} else {
release_statement_context(t_thrd.shemem_ptr_cxt.MyBEEntry, __FUNCTION__, __LINE__);
}
WaitUntilLWLockInfoNeverAccess(beentry);
* Clear the thread-local pointer MyBEEntry
* so that it can not be revisited.
*/
t_thrd.shemem_ptr_cxt.MyBEEntry = NULL;
}
* Shut down a single session's statistics reporting at session close.
*
* Flush any remaining statistics counts out to the collector.
* Without this, operations triggered during backend exit (such as
* temp table deletions) won't be counted.
*/
void pgstat_beshutdown_session(int ctrl_index)
{
if (!t_thrd.proc_cxt.proc_exit_inprogress) {
volatile PgBackendStatus* beentry = &t_thrd.shemem_ptr_cxt.BackendStatusArray[ctrl_index];
* If we got as far as discovering our own database ID, we can report what
* we did to the collector. Otherwise, we'd be sending an invalid
* database ID, so forget it. (This means that accesses to pg_database
* during failed backend starts might never get counted.)
*/
if (OidIsValid(beentry->st_databaseid))
pgstat_report_stat(true);
* Clear my status entry, following the protocol of bumping st_changecount
* before and after. We use a volatile pointer here to ensure the
* compiler doesn't try to get cute.
*/
pgstat_increment_changecount_before(beentry);
beentry->st_procpid = 0;
beentry->st_sessionid = 0;
beentry->globalSessionId.sessionId = 0;
beentry->globalSessionId.nodeId = 0;
beentry->globalSessionId.seq = 0;
* make sure st_changecount is an even before release it.
*
* In case some thread was interrupted by SIGTERM at any time with a mess st_changecount
* in PgBackendStatus, PgstatCollectorMain may hang-up in waiting its change to even and
* can not exit after receiving SIGTERM signal
*/
do {
pgstat_increment_changecount_after(beentry);
} while ((beentry->st_changecount & 1) != 0);
* pgstat_beshutdown_session will be called in thread pool mode:
* if t_thrd.proc_cxt.proc_exit_inprogress is true, thread backend entry and
* session backend entry can be reused by other backend(CleanupInvalidationState
* is called before), so backend entry cann't be accessed during this stage.
* when false, just case that thread pool worker is detaching or closing session,
* so we need to release statemement context.
*/
release_statement_context(&t_thrd.shemem_ptr_cxt.BackendStatusArray[ctrl_index], __FUNCTION__, __LINE__);
* During process exit, t_thrd.proc may have been cleared, so that
* we can no longer hold LWlocks.
* Maybe we should move clean up of session before worker thread
* clears its share memory stuffs.
*/
DetachMySessionTimeEntry(&t_thrd.shemem_ptr_cxt.sessionTimeArray[ctrl_index]);
START_CRIT_SECTION();
beentry->st_lw_is_cleanning_flag = true;
pg_read_barrier();
while (beentry->st_lw_access_flag) {
(void)sched_yield();
pg_usleep(1);
}
beentry->lw_held_num = NULL;
beentry->lw_held_locks = NULL;
beentry->st_lw_is_cleanning_flag = false;
END_CRIT_SECTION();
}
t_thrd.shemem_ptr_cxt.sessionStatArray[ctrl_index].isValid = false;
t_thrd.shemem_ptr_cxt.sessionTimeArray[ctrl_index].changeCount++;
t_thrd.shemem_ptr_cxt.sessionTimeArray[ctrl_index].isActive = false;
t_thrd.shemem_ptr_cxt.sessionTimeArray[ctrl_index].changeCount++;
Assert((t_thrd.shemem_ptr_cxt.sessionTimeArray[ctrl_index].changeCount & 1) == 0);
t_thrd.shemem_ptr_cxt.sessionMemoryArray[ctrl_index].isValid = false;
}
* pgstat_report_activity() -
*
* Called from tcop/postgres.c to report what the backend is actually doing
* (usually "<IDLE>" or the start of the query to be executed).
*
* All updates of the status entry follow the protocol of bumping
* st_changecount before and after. We use a volatile pointer here to
* ensure the compiler doesn't try to get cute.
* ----------
*/
void pgstat_report_activity(BackendState state, const char* cmd_str)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
TimestampTz start_timestamp;
TimestampTz current_timestamp;
int len = 0;
errno_t rc = EOK;
TRACE_POSTGRESQL_STATEMENT_STATUS(cmd_str);
if (beentry == NULL)
return;
if (!u_sess->attr.attr_common.pgstat_track_activities && beentry->st_state == STATE_DISABLED)
return;
* To minimize the time spent modifying the entry, fetch all the needed
* data first.
*/
current_timestamp = GetCurrentTimestamp();
if (!u_sess->attr.attr_common.pgstat_track_activities && beentry->st_state != STATE_DISABLED) {
* Track activities is disabled, but we have a non-disabled state set.
* That means the status changed - so as our last update, tell the
* collector that we disabled it and will no longer update.
*/
pgstat_increment_changecount_before(beentry);
beentry->st_state = STATE_DISABLED;
beentry->st_state_start_timestamp = current_timestamp;
pgstat_increment_changecount_after(beentry);
return;
}
* Fetch more data before we start modifying the entry
*/
if (IS_SINGLE_NODE || IS_PGXC_DATANODE)
start_timestamp = GetCurrentStatementLocalStartTimestamp();
else
start_timestamp = GetCurrentStatementStartTimestamp();
if (cmd_str != NULL) {
len = pg_mbcliplen(cmd_str, strlen(cmd_str), g_instance.attr.attr_common.pgstat_track_activity_query_size - 1);
}
* Now update the status entry
*/
pgstat_increment_changecount_before(beentry);
beentry->st_state = state;
beentry->st_state_start_timestamp = current_timestamp;
if (cmd_str != NULL) {
char *mask_string = NULL;
if (len == g_instance.attr.attr_common.pgstat_track_activity_query_size - 1 &&
t_thrd.mem_cxt.mask_password_mem_cxt != NULL) {
mask_string = maskPassword(cmd_str);
}
if (mask_string == NULL) {
rc = memcpy_s((char*)beentry->st_activity, g_instance.attr.attr_common.pgstat_track_activity_query_size,
cmd_str, len);
securec_check(rc, "\0", "\0");
} else {
int copy_len = strlen(mask_string);
if (len < copy_len) {
copy_len = len;
}
rc = memcpy_s((char*)beentry->st_activity, g_instance.attr.attr_common.pgstat_track_activity_query_size,
mask_string, copy_len);
securec_check(rc, "\0", "\0");
pfree(mask_string);
}
beentry->st_activity[len] = '\0';
beentry->st_activity_start_timestamp = start_timestamp;
}
pgstat_increment_changecount_after(beentry);
}
* pgstat_report_appname() -
*
* Called to update our application name.
* ----------
*/
void pgstat_report_appname(const char* appname)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
int len;
errno_t rc;
if (beentry == NULL)
return;
len = pg_mbcliplen(appname, strlen(appname), NAMEDATALEN - 1);
* Update my status entry, following the protocol of bumping
* st_changecount before and after. We use a volatile pointer here to
* ensure the compiler doesn't try to get cute.
*/
pgstat_increment_changecount_before(beentry);
if (len > 0) {
rc = memcpy_s((char*)beentry->st_appname, len, appname, len);
securec_check(rc, "\0", "\0");
}
beentry->st_appname[len] = '\0';
pgstat_increment_changecount_after(beentry);
}
* pgstat_report_conninfo() -
*
* Called to update our connection info.
* ----------
*/
void pgstat_report_conninfo(const char* conninfo)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
int len;
errno_t rc;
if (beentry == NULL)
return;
len = pg_mbcliplen(conninfo, strlen(conninfo), CONNECTIONINFO_LEN - 1);
* Update my status entry, following the protocol of bumping
* st_changecount before and after. We use a volatile pointer here to
* ensure the compiler doesn't try to get cute.
*/
pgstat_increment_changecount_before(beentry);
if (len > 0) {
rc = memcpy_s((char*)beentry->st_conninfo, len, conninfo, len);
securec_check(rc, "\0", "\0");
}
beentry->st_conninfo[len] = '\0';
pgstat_increment_changecount_after(beentry);
}
* Report current transaction start timestamp as the specified value.
* Zero means there is no active transaction.
*/
void pgstat_report_xact_timestamp(TimestampTz tstamp)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (!u_sess->attr.attr_common.pgstat_track_activities || (beentry == NULL))
return;
* Update my status entry, following the protocol of bumping
* st_changecount before and after. We use a volatile pointer here to
* ensure the compiler doesn't try to get cute.
*/
pgstat_increment_changecount_before(beentry);
beentry->st_xact_start_timestamp = tstamp;
pgstat_increment_changecount_after(beentry);
}
void pgstat_report_global_session_id(GlobalSessionId globalSessionId)
{
#ifndef ENABLE_MULTIPLE_NODES
return;
#endif
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (IS_PGSTATE_TRACK_UNDEFINE || globalSessionId.sessionId == 0)
return;
pgstat_increment_changecount_before(beentry);
beentry->globalSessionId.sessionId = globalSessionId.sessionId;
beentry->globalSessionId.nodeId = globalSessionId.nodeId;
beentry->globalSessionId.seq = globalSessionId.seq;
pgstat_increment_changecount_after(beentry);
}
void pgstat_report_unique_sql_id(bool resetUniqueSql)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (IS_PGSTATE_TRACK_UNDEFINE)
return;
if (resetUniqueSql) {
beentry->st_unique_sql_key.unique_sql_id = 0;
beentry->st_unique_sql_key.cn_id = 0;
beentry->st_unique_sql_key.user_id = 0;
} else {
beentry->st_unique_sql_key.unique_sql_id = u_sess->unique_sql_cxt.unique_sql_id;
beentry->st_unique_sql_key.cn_id = u_sess->unique_sql_cxt.unique_sql_cn_id;
beentry->st_unique_sql_key.user_id = u_sess->unique_sql_cxt.unique_sql_user_id;
}
}
void pgstat_report_queryid(uint64 queryid)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (IS_PGSTATE_TRACK_UNDEFINE)
return;
* Since this is a single-byte field in a struct that only this process
* may modify, there seems no need to bother with the st_changecount
* protocol. The update must appear atomic in any case.
*/
beentry->st_queryid = queryid;
}
void pgstat_report_trace_id(knl_u_trace_context *trace_cxt, bool is_report_trace_id)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (IS_PGSTATE_TRACK_UNDEFINE)
return;
if (is_report_trace_id) {
errno_t rc =
memcpy_s((void*)beentry->trace_cxt.trace_id, MAX_TRACE_ID_SIZE, trace_cxt->trace_id,
strlen(trace_cxt->trace_id) + 1);
securec_check(rc, "\0", "\0");
}
}
void pgstat_report_jobid(uint64 jobid)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (IS_PGSTATE_TRACK_UNDEFINE)
return;
* Update jobid when it starts
*/
beentry->st_jobid = jobid;
}
void pgstat_report_parent_sessionid(uint64 sessionid, uint32 level)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (IS_PGSTATE_TRACK_UNDEFINE)
return;
* Since this is a single-byte field in a struct that only this process
* may modify, there seems no need to bother with the st_changecount
* protocol. The update must appear atomic in any case.
*/
beentry->st_sessionid = sessionid;
beentry->st_parent_sessionid = sessionid;
beentry->st_thread_level = level;
}
void pgstat_report_bgworker_parent_sessionid(uint64 sessionid)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (IS_PGSTATE_TRACK_UNDEFINE)
return;
beentry->st_parent_sessionid = sessionid;
}
* pgstat_report_connected_gtm_host() -
*
* Called from InitGTM to report the latest connected GTM host index.
* Also Called from CloseGTM to clear the above information.
*
* NB: this *must* be able to survive being called before MyBEEntry has been
* initialized.
* ----------
*/
void pgstat_report_connected_gtm_host(GtmHostIndex gtm_host)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (beentry == NULL)
return;
* Since this is an enumeration-type field in a struct that
* only this process may modify, there seems no need
* to bother with the st_changecount protocol.
*
* The update must appear atomic in any case.
*/
beentry->st_gtmhost = gtm_host;
}
* pgstat_report_connected_gtm_timeline() -
*
* Called from InitGTM_Reporttimeline to report
* the latest connected GTM host timeline.
* Also Called from CloseGTM to clear the above information.
*
* NB: this *must* be able to survive being called before MyBEEntry has been
* initialized.
* ----------
*/
void pgstat_report_connected_gtm_timeline(GTM_Timeline gtm_timeline)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (beentry == NULL)
return;
* Since this is an uint32-type field in a struct that
* only this process may modify, there seems no need
* to bother with the st_changecount protocol.
*
* The update must appear atomic in any case.
*/
beentry->st_gtmtimeline = gtm_timeline;
}
void pgstat_report_smpid(uint32 smpid)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (IS_PGSTATE_TRACK_UNDEFINE)
return;
* Since this is a single-byte field in a struct that only this process
* may modify, there seems no need to bother with the st_changecount
* protocol. The update must appear atomic in any case.
*/
beentry->st_smpid = smpid;
}
* report blocking session into waitLockThrd's PgBackendStatus
*/
void pgstat_report_blocksid(void* waitLockThrd, uint64 blockSessionId)
{
if (IS_PGSTATE_TRACK_UNDEFINE)
return;
knl_thrd_context* thrd = (knl_thrd_context*)waitLockThrd;
volatile PgBackendStatus* beentry = thrd->shemem_ptr_cxt.MyBEEntry;
if (beentry->st_block_sessionid != blockSessionId) {
if (blockSessionId == 0) {
ereport(DEBUG1,
(errmsg("thread %lu waiting for lock ends", thrd->proc_cxt.MyProcPid)));
} else if (thrd->proc->waitLock != NULL) {
ereport(DEBUG1,
(errmsg("thread %lu waiting for %s on %s, blocking session %lu",
thrd->proc_cxt.MyProcPid,
GetLockmodeName(thrd->proc->waitLock->tag.locktag_lockmethodid, thrd->proc->waitLockMode),
LocktagToString(thrd->proc->waitLock->tag),
blockSessionId)));
}
* NOTE: use 'volatile' to control concurrency instead of 'changecount' here since this
* mothod could be not only invoked by itself thread but also other thread. 'changecount'
* can work well only when write is done by one thread.
*/
beentry->st_block_sessionid = blockSessionId;
}
}
* updateMaxValueForAtomicType - using atomic type to store max value,
* we need update the max value by using atomic method
*/
static void updateMaxValueForAtomicType(uint64 new_val, uint64* max)
{
uint64 prev;
do
prev = *max;
while (prev < new_val && !pg_atomic_compare_exchange_u64(max, &prev, new_val));
}
* updateMinValueForAtomicType - update ming value for atomic type
*/
static void updateMinValueForAtomicType(uint64 new_val, uint64* mix)
{
uint64 prev;
do
prev = *mix;
while ((prev == 0 || prev > new_val) && !pg_atomic_compare_exchange_u64(mix, &prev, new_val));
}
* @Description: remove user from WaitCountHashTbl and WaitCountStatusList for the user does not exist.
* @in -userid: oid of the user who was dropped
* @out - void
*/
static void RemoveWaitCount(Oid userId)
{
if (g_instance.stat_cxt.WaitCountHashTbl == NULL) {
return;
}
WaitCountHashValue* waitCountIdx =
(WaitCountHashValue*)hash_search(g_instance.stat_cxt.WaitCountHashTbl, &userId, HASH_REMOVE, NULL);
if (waitCountIdx == NULL) {
return;
}
int dataId = waitCountIdx->idx % WAIT_COUNT_ARRAY_SIZE;
int listNodeId = waitCountIdx->idx / WAIT_COUNT_ARRAY_SIZE;
ListCell* lc = list_nth_cell(g_instance.stat_cxt.WaitCountStatusList, listNodeId);
PgStat_WaitCountStatusCell* waitCountStatusCell = (PgStat_WaitCountStatusCell*)lfirst(lc);
pg_atomic_write_u32(&waitCountStatusCell->WaitCountArray[dataId].userid, 0);
}
bool pg_check_authid(Oid authid)
{
HeapTuple roletup = NULL;
* Get the pg_authid entry and print the result
*/
roletup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(authid));
if (HeapTupleIsValid(roletup)) {
ReleaseSysCache(roletup);
return true;
} else {
return false;
}
}
* @Description: check whether the user exists, and remove sql count if not exist and required.
* @in1 - userid: user oid.
* @in2 - removeCount: if remove sql count when user does not exist.
* @out - bool
*/
bool CheckUserExist(Oid userId, bool removeCount)
{
if (!OidIsValid(userId)) {
return false;
}
bool isExist = pg_check_authid(userId);
if (!isExist && removeCount) {
LWLockRelease(WaitCountHashLock);
LWLockAcquire(WaitCountHashLock, LW_EXCLUSIVE);
RemoveWaitCount(userId);
LWLockRelease(WaitCountHashLock);
LWLockAcquire(WaitCountHashLock, LW_SHARED);
}
return isExist;
}
#define UPDATE_SQL_COUNT(count, elapseTime) \
do { \
TimestampTz duration = GetCurrentTimestamp() - GetCurrentStatementLocalStartTimestamp(); \
duration = (duration == 0) ? 1 : duration; \
pg_atomic_fetch_add_u64(&(count), 1); \
pg_atomic_fetch_add_u64(&((elapseTime).total_time), duration); \
updateMaxValueForAtomicType(duration, &((elapseTime).max_time)); \
updateMinValueForAtomicType(duration, &((elapseTime).min_time)); \
} while (0)
* @Description: according to wait_event_info to add sql count for user,
* add action realize by pg_atomic_fetch_add_u64 function
* @in wait_event_info - one kind of WaitEventSQL
* @out - void
*/
void pgstat_report_wait_count(unsigned int wait_event_info)
{
Oid userid;
WaitCountHashValue* WaitCountIdx = NULL;
int dataid;
int listNodeid;
uint32 classId = wait_event_info & 0xFF000000;
LWLockAcquire(WaitCountHashLock, LW_SHARED);
if (g_instance.stat_cxt.WaitCountHashTbl == NULL) {
ereport(LOG, (errcode(ERRCODE_WARNING), (errmsg("sql count hashtable: WaitCountHashTbl is not initialized!"))));
LWLockRelease(WaitCountHashLock);
return;
}
userid = GetUserId();
WaitCountIdx = (WaitCountHashValue*)hash_search(g_instance.stat_cxt.WaitCountHashTbl, &userid, HASH_FIND, NULL);
if (WaitCountIdx == NULL) {
ereport(LOG,
(errcode(ERRCODE_WARNING), (errmsg("can not find the user in sql count hashtable: userid %u", userid))));
LWLockRelease(WaitCountHashLock);
if (!CheckUserExist(userid, false)) {
return;
}
LWLockAcquire(WaitCountHashLock, LW_EXCLUSIVE);
WaitCountIdx = (WaitCountHashValue*)hash_search(g_instance.stat_cxt.WaitCountHashTbl, &userid, HASH_FIND, NULL);
if (WaitCountIdx == NULL) {
initWaitCount(userid);
ereport(LOG,
(errcode(ERRCODE_WARNING),
(errmsg("success to insert user into WaitCountHashTbl: userid %u!", userid))));
}
LWLockRelease(WaitCountHashLock);
LWLockAcquire(WaitCountHashLock, LW_SHARED);
WaitCountIdx = (WaitCountHashValue*)hash_search(g_instance.stat_cxt.WaitCountHashTbl, &userid, HASH_FIND, NULL);
if (WaitCountIdx == NULL) {
ereport(WARNING,
(errcode(ERRCODE_WARNING),
(errmsg("failed to insert user into WaitCountHashTbl: userid %u!, sql count failed!", userid))));
LWLockRelease(WaitCountHashLock);
return;
}
}
dataid = WaitCountIdx->idx % WAIT_COUNT_ARRAY_SIZE;
listNodeid = WaitCountIdx->idx / WAIT_COUNT_ARRAY_SIZE;
ListCell* lc = NULL;
PgStat_WaitCountStatusCell* WaitCountStatusCell = NULL;
lc = list_nth_cell(g_instance.stat_cxt.WaitCountStatusList, listNodeid);
WaitCountStatusCell = (PgStat_WaitCountStatusCell*)lfirst(lc);
if (classId == PG_WAIT_SQL) {
WaitEventSQL w = (WaitEventSQL)wait_event_info;
switch (w) {
case WAIT_EVENT_SQL_SELECT: {
UPDATE_SQL_COUNT(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_select,
WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.selectElapse);
} break;
case WAIT_EVENT_SQL_UPDATE: {
UPDATE_SQL_COUNT(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_update,
WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.updateElapse);
} break;
case WAIT_EVENT_SQL_INSERT: {
UPDATE_SQL_COUNT(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_insert,
WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.insertElapse);
} break;
case WAIT_EVENT_SQL_DELETE: {
UPDATE_SQL_COUNT(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_delete,
WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.deleteElapse);
} break;
case WAIT_EVENT_SQL_MERGEINTO:
pg_atomic_fetch_add_u64(&(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_mergeinto), 1);
break;
case WAIT_EVENT_SQL_DDL:
pg_atomic_fetch_add_u64(&(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_ddl), 1);
break;
case WAIT_EVENT_SQL_DML:
pg_atomic_fetch_add_u64(&(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_dml), 1);
break;
case WAIT_EVENT_SQL_DCL:
pg_atomic_fetch_add_u64(&(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_dcl), 1);
break;
case WAIT_EVENT_SQL_TCL:
pg_atomic_fetch_add_u64(&(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_tcl), 1);
break;
default:
break;
}
}
LWLockRelease(WaitCountHashLock);
}
* pgstat_report_waiting_on_resource() -
*
* Called from GTM to report beginning or end of a wait on reserving memory.
*
* ----------
*/
void pgstat_report_waiting_on_resource(WorkloadManagerEnqueueState waiting)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (IS_PGSTATE_TRACK_UNDEFINE)
return;
beentry->st_waiting_on_resource = waiting;
switch (waiting) {
case STATE_ACTIVE_STATEMENTS:
(void)pgstat_report_waitstatus(STATE_WAIT_ACTIVE_STATEMENT);
break;
case STATE_MEMORY:
(void)pgstat_report_waitstatus(STATE_WAIT_MEMORY);
break;
case STATE_NO_ENQUEUE:
(void)pgstat_report_waitstatus(STATE_WAIT_UNDEFINED);
break;
default:
(void)pgstat_report_waitstatus(STATE_WAIT_UNDEFINED);
break;
}
}
* pgstat_report_statement_wlm_status() -
*
* set current statement wlm status, include block time, cpu time.
* ----------
*/
void pgstat_report_statement_wlm_status()
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (IS_PGSTATE_TRACK_UNDEFINE)
return;
WLMGetStatistics((TimestampTz*)&beentry->st_block_start_time,
(TimestampTz*)&beentry->st_elapsed_start_time,
(WLMStatistics*)&beentry->st_backstat);
}
* pgstat_refresh_statement_wlm_time() -
*
* refresh the block time and elapsed time for a statement state.
* ----------
*/
void pgstat_refresh_statement_wlm_time(volatile PgBackendStatus* beentry)
{
if (NULL != beentry) {
WLMStatistics* backstat = (WLMStatistics*)&beentry->st_backstat;
if (!StringIsValid(backstat->status))
return;
if (strcmp(backstat->status, "pending") == 0) {
backstat->blocktime = (GetCurrentTimestamp() - beentry->st_block_start_time) / USECS_PER_SEC;
backstat->elapsedtime = 0;
} else if (strcmp(backstat->status, "running") == 0)
backstat->elapsedtime = (GetCurrentTimestamp() - beentry->st_elapsed_start_time) / USECS_PER_SEC;
WLMDataIndicator<int64> indicator;
errno_t errval = memset_s(&indicator, sizeof(indicator), 0, sizeof(indicator));
securec_check_errval(errval, , LOG);
WLMGetCPUDataIndicator((PgBackendStatus*)beentry, &indicator);
backstat->maxcputime = indicator.max_value / MSECS_PER_SEC;
backstat->totalcputime = indicator.total_value / MSECS_PER_SEC;
backstat->skewpercent = indicator.skew_percent;
}
}
* pgstat_increase_session_spill() -
*
* increase current session spill count.
* ----------
*/
void pgstat_increase_session_spill(void)
{
gs_atomic_add_32(&t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->spillCount, 1);
}
* pgstat_increase_session_spill_size() -
*
* increase current session spill size.
* ----------
*/
void pgstat_increase_session_spill_size(int64 size)
{
gs_atomic_add_64(&t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->spillSize, size);
}
* pgstat_add_warning_early_spill() -
*
* add warning for early spill.
* ----------
*/
void pgstat_add_warning_early_spill()
{
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->warning |= (1 << WLM_WARN_EARLY_SPILL);
}
* pgstat_add_warning_spill_on_memory_spread() -
*
* add warning for spill on memory spread.
* ----------
*/
void pgstat_add_warning_spill_on_memory_spread()
{
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->warning |= (1 << WLM_WARN_SPILL_ON_MEMORY_SPREAD);
}
* pgstat_add_warning_spill_on_memory_spread() -
*
* add warning for spill on memory spread.
* ----------
*/
void pgstat_add_warning_hash_conflict()
{
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->warning |= (1 << WLM_WARN_HASH_CONFLICT);
}
* pgstat_set_io_state() -
*
* set backend io state.
* ----------
*/
void pgstat_set_io_state(WorkloadManagerIOState iostate)
{
if (!u_sess->attr.attr_common.pgstat_track_activities)
return;
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (beentry == NULL)
return;
beentry->st_io_state = iostate;
u_sess->wlm_cxt->wlm_params.iostate = (unsigned char)iostate;
}
* pgstat_set_stmt_tag() -
*
* set backend statement tag.
* ----------
*/
void pgstat_set_stmt_tag(WorkloadManagerStmtTag stmttag)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (!u_sess->attr.attr_common.pgstat_track_activities || (beentry == NULL))
return;
beentry->st_stmttag = stmttag;
}
WaitInfo* read_current_instr_wait_info(void)
{
volatile PgBackendStatus* beentry = NULL;
int i;
errno_t rc;
WaitInfo* gsInstrWaitInfo = (WaitInfo*)palloc0(sizeof(WaitInfo) * 1);
beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray;
for (i = 1; i <= BackendStatusArray_size; i++) {
* Follow the protocol of retrying if st_changecount changes while we
* copy the entry, or if it's odd. (The check for odd is needed to
* cover the case where we are able to completely copy the entry while
* the source backend is between increment steps.) We use a volatile
* pointer here to ensure the compiler doesn't try to get cute.
*/
WaitInfo waitinfo;
for (;;) {
int before_changecount;
int after_changecount;
pgstat_save_changecount_before(beentry, before_changecount);
rc = memcpy_s(&waitinfo, sizeof(WaitInfo), (WaitInfo*)&beentry->waitInfo, sizeof(WaitInfo));
securec_check(rc, "", "");
pgstat_save_changecount_after(beentry, after_changecount);
if (before_changecount == after_changecount && (before_changecount & 1) == 0)
break;
CHECK_FOR_INTERRUPTS();
}
CollectWaitInfo(gsInstrWaitInfo, waitinfo.status_info, waitinfo.event_info);
beentry++;
}
return gsInstrWaitInfo;
}
* pgstat_reset_current_status() -
*
* reset current status
* ----------
*/
void pgstat_reset_current_status(void)
{
if (u_sess->stat_cxt.pgStatLocalContext) {
MemoryContextDelete(u_sess->stat_cxt.pgStatLocalContext);
u_sess->stat_cxt.pgStatLocalContext = NULL;
}
u_sess->stat_cxt.localBackendStatusTable = NULL;
}
* pgstat_get_waitlock() -
*
* Return true if waiting for a lmgr lock.
* ----------
*/
bool pgstat_get_waitlock(uint32 wait_event_info)
{
uint32 classId;
classId = wait_event_info & 0xFF000000;
return classId == PG_WAIT_LOCK;
}
* pgstat_get_wait_event() -
*
* Return a string representing the current wait event, backend is
* waiting on.
*/
const char* pgstat_get_wait_event(uint32 wait_event_info)
{
uint32 classId;
uint16 eventId;
const char* event_name = NULL;
classId = wait_event_info & 0xFF000000;
eventId = wait_event_info & 0x0000FFFF;
switch (classId) {
case PG_WAIT_LWLOCK:
event_name = GetLWLockIdentifier(classId, eventId);
break;
case PG_WAIT_LOCK:
event_name = GetLockNameFromTagType(eventId);
break;
case PG_WAIT_IO: {
WaitEventIO w = (WaitEventIO)wait_event_info;
event_name = pgstat_get_wait_io(w);
break;
}
case PG_WAIT_DMS: {
WaitEventDMS w = (WaitEventDMS)wait_event_info;
event_name = pgstat_get_wait_dms(w);
break;
}
default:
event_name = "unknown wait event";
break;
}
return event_name;
}
void LWLockReportWaitFailed(LWLock* lock)
{
pgstat_report_wait_lock_failed(PG_WAIT_LWLOCK | lock->tranche);
}
* pgstat_get_wait_io() -
*
* Convert WaitEventIO to string.
* ----------
*/
const char* pgstat_get_wait_io(WaitEventIO w)
{
const char* event_name = "unknown wait event";
switch (w) {
case WAIT_EVENT_BUFFILE_READ:
event_name = "BufFileRead";
break;
case WAIT_EVENT_BUFFILE_WRITE:
event_name = "BufFileWrite";
break;
case WAIT_EVENT_BUF_HASH_SEARCH:
event_name = "BufHashTableSearch";
break;
case WAIT_EVENT_BUF_STRATEGY_GET:
event_name = "StrategyGetBuffer";
break;
case WAIT_EVENT_CONTROL_FILE_READ:
event_name = "ControlFileRead";
break;
case WAIT_EVENT_CONTROL_FILE_SYNC:
event_name = "ControlFileSync";
break;
case WAIT_EVENT_CONTROL_FILE_SYNC_UPDATE:
event_name = "ControlFileSyncUpdate";
break;
case WAIT_EVENT_CONTROL_FILE_WRITE:
event_name = "ControlFileWrite";
break;
case WAIT_EVENT_CONTROL_FILE_WRITE_UPDATE:
event_name = "ControlFileWriteUpdate";
break;
case WAIT_EVENT_COPY_FILE_READ:
event_name = "CopyFileRead";
break;
case WAIT_EVENT_COPY_FILE_WRITE:
event_name = "CopyFileWrite";
break;
case WAIT_EVENT_DATA_FILE_EXTEND:
event_name = "DataFileExtend";
break;
case WAIT_EVENT_DATA_FILE_IMMEDIATE_SYNC:
event_name = "DataFileImmediateSync";
break;
case WAIT_EVENT_DATA_FILE_PREFETCH:
event_name = "DataFilePrefetch";
break;
case WAIT_EVENT_DATA_FILE_READ:
event_name = "DataFileRead";
break;
case WAIT_EVENT_DATA_FILE_SYNC:
event_name = "DataFileSync";
break;
case WAIT_EVENT_DATA_FILE_TRUNCATE:
event_name = "DataFileTruncate";
break;
case WAIT_EVENT_DATA_FILE_WRITE:
event_name = "DataFileWrite";
break;
case WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ:
event_name = "LockFileAddToDataDirRead";
break;
case WAIT_EVENT_LOCK_FILE_ADDTODATADIR_SYNC:
event_name = "LockFileAddToDataDirSync";
break;
case WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE:
event_name = "LockFileAddToDataDirWrite";
break;
case WAIT_EVENT_LOCK_FILE_CREATE_READ:
event_name = "LockFileCreateRead";
break;
case WAIT_EVENT_LOCK_FILE_CREATE_SYNC:
event_name = "LockFileCreateSync";
break;
case WAIT_EVENT_LOCK_FILE_CREATE_WRITE:
event_name = "LockFileCreateWRITE";
break;
case WAIT_EVENT_RELATION_MAP_READ:
event_name = "RelationMapRead";
break;
case WAIT_EVENT_RELATION_MAP_SYNC:
event_name = "RelationMapSync";
break;
case WAIT_EVENT_RELATION_MAP_WRITE:
event_name = "RelationMapWrite";
break;
case WAIT_EVENT_REPLICATION_SLOT_READ:
event_name = "ReplicationSlotRead";
break;
case WAIT_EVENT_REPLICATION_SLOT_RESTORE_SYNC:
event_name = "ReplicationSlotRestoreSync";
break;
case WAIT_EVENT_REPLICATION_SLOT_SYNC:
event_name = "ReplicationSlotSync";
break;
case WAIT_EVENT_REPLICATION_SLOT_WRITE:
event_name = "ReplicationSlotWrite";
break;
case WAIT_EVENT_SLRU_FLUSH_SYNC:
event_name = "SLRUFlushSync";
break;
case WAIT_EVENT_SLRU_READ:
event_name = "SLRURead";
break;
case WAIT_EVENT_SLRU_SYNC:
event_name = "SLRUSync";
break;
case WAIT_EVENT_SLRU_WRITE:
event_name = "SLRUWrite";
break;
case WAIT_EVENT_TWOPHASE_FILE_READ:
event_name = "TwophaseFileRead";
break;
case WAIT_EVENT_TWOPHASE_FILE_SYNC:
event_name = "TwophaseFileSync";
break;
case WAIT_EVENT_TWOPHASE_FILE_WRITE:
event_name = "TwophaseFileWrite";
break;
case WAIT_EVENT_WAL_BOOTSTRAP_SYNC:
event_name = "WALBootstrapSync";
break;
case WAIT_EVENT_WAL_BOOTSTRAP_WRITE:
event_name = "WALBootstrapWrite";
break;
case WAIT_EVENT_WAL_COPY_READ:
event_name = "WALCopyRead";
break;
case WAIT_EVENT_WAL_COPY_SYNC:
event_name = "WALCopySync";
break;
case WAIT_EVENT_WAL_COPY_WRITE:
event_name = "WALCopyWrite";
break;
case WAIT_EVENT_WAL_INIT_SYNC:
event_name = "WALInitSync";
break;
case WAIT_EVENT_WAL_INIT_WRITE:
event_name = "WALInitWrite";
break;
case WAIT_EVENT_WAL_READ:
event_name = "WALRead";
break;
case WAIT_EVENT_WAL_SYNC_METHOD_ASSIGN:
event_name = "WALSyncMethodAssign";
break;
case WAIT_EVENT_WAL_WRITE:
event_name = "WALWrite";
break;
case WAIT_EVENT_WAL_BUFFER_FULL:
event_name = "WALBufferFull";
break;
case WAIT_EVENT_WAL_BUFFER_ACCESS:
event_name = "WALBufferAccess";
break;
case WAIT_EVENT_DW_READ:
event_name = "DoubleWriteFileRead";
break;
case WAIT_EVENT_DW_WRITE:
event_name = "DoubleWriteFileWrite";
break;
case WAIT_EVENT_DW_SINGLE_POS:
event_name = "DWSingleFlushGetPos";
break;
case WAIT_EVENT_DW_SINGLE_WRITE:
event_name = "DWSingleFlushWrite";
break;
case WAIT_EVENT_PREDO_PROCESS_PENDING:
event_name = "PredoProcessPending";
break;
case WAIT_EVENT_PREDO_APPLY:
event_name = "PredoApply";
break;
case WAIT_EVENT_DISABLE_CONNECT_FILE_READ:
event_name = "DisableConnectFileRead";
break;
case WAIT_EVENT_DISABLE_CONNECT_FILE_SYNC:
event_name = "DisableConnectFileSync";
break;
case WAIT_EVENT_DISABLE_CONNECT_FILE_WRITE:
event_name = "DisableConnectFileWrite";
break;
case WAIT_EVENT_MPFL_INIT:
event_name = "MPFL_INIT";
break;
case WAIT_EVENT_MPFL_READ:
event_name = "MPFL_READ";
break;
case WAIT_EVENT_MPFL_WRITE:
event_name = "MPFL_WRITE";
break;
case WAIT_EVENT_OBS_LIST:
event_name = "OBSList";
break;
case WAIT_EVENT_OBS_READ:
event_name = "OBSRead";
break;
case WAIT_EVENT_OBS_WRITE:
event_name = "OBSWrite";
break;
case WAIT_EVENT_LOGCTRL_SLEEP:
event_name = "LOGCTRL_SLEEP";
break;
case WAIT_EVENT_COMPRESS_ADDRESS_FILE_FLUSH:
event_name = "PCA_FLUSH";
break;
case WAIT_EVENT_COMPRESS_ADDRESS_FILE_SYNC:
event_name = "PCA_SYNC";
break;
case IO_EVENT_NUM:
break;
case WAIT_EVENT_UNDO_FILE_PREFETCH:
event_name = "UndoFilePrefetch";
break;
case WAIT_EVENT_UNDO_FILE_READ:
event_name = "UndoFileRead";
break;
case WAIT_EVENT_UNDO_FILE_WRITE:
event_name = "UndoFileWrite";
break;
case WAIT_EVENT_UNDO_FILE_SYNC:
event_name = "UndoFileSync";
break;
case WAIT_EVENT_UNDO_FILE_EXTEND:
event_name = "UndoFileExtend";
break;
case WAIT_EVENT_UNDO_FILE_UNLINK:
event_name = "UndoFileUnlink";
break;
case WAIT_EVENT_UNDO_META_SYNC:
event_name = "UndoMetaSync";
break;
case WAIT_EVENT_LOGICAL_SYNC_DATA:
event_name = "LogicalSyncData";
break;
case WAIT_EVENT_LOGICAL_SYNC_STATE_CHANGE:
event_name = "LogicalSyncStateChange";
break;
case WAIT_EVENT_REPLICATION_ORIGIN_DROP:
event_name = "ReplicationOriginDrop";
break;
case WAIT_EVENT_REPLICATION_SLOT_DROP:
event_name = "ReplicationSlotDrop";
break;
default:
event_name = "unknown wait event";
break;
}
return event_name;
}
* pgstat_get_wait_dms() -
*
* Convert WaitEventDMS to string.
* ----------
*/
const char* pgstat_get_wait_dms(WaitEventDMS w)
{
const char* event_name = "unknown wait event";
switch (w) {
case WAIT_EVENT_IDLE_WAIT:
event_name = "IdleWait";
break;
case WAIT_EVENT_GC_BUFFER_BUSY:
event_name = "GcBufferBusy";
break;
case WAIT_EVENT_DCS_REQ_MASTER4PAGE_1WAY:
event_name = "DcsReqMaster4Page1Way";
break;
case WAIT_EVENT_DCS_REQ_MASTER4PAGE_2WAY:
event_name = "DcsReqMaster4Page2Way";
break;
case WAIT_EVENT_DCS_REQ_MASTER4PAGE_3WAY:
event_name = "DcsReqMaster4Page3Way";
break;
case WAIT_EVENT_DCS_REQ_MASTER4PAGE_TRY:
event_name = "DcsReqMaster4PageTry";
break;
case WAIT_EVENT_DCS_REQ_OWNER4PAGE:
event_name = "DcsReqOwner4Page";
break;
case WAIT_EVENT_DCS_CLAIM_OWNER:
event_name = "DcsCliamOwner";
break;
case WAIT_EVENT_DCS_RELEASE_OWNER:
event_name = "DcsReleaseOwner";
break;
case WAIT_EVENT_DCS_INVLDT_SHARE_COPY_REQ:
event_name = "DcsInvldtShareCopyReq";
break;
case WAIT_EVENT_DCS_INVLDT_SHARE_COPY_PROCESS:
event_name = "DcsInvldtShareCopyProcess";
break;
case WAIT_EVENT_DCS_TRANSFER_PAGE_LATCH:
event_name = "DcsTransferPageLatch";
break;
case WAIT_EVENT_DCS_TRANSFER_PAGE_READONLY2X:
event_name = "DcsTransferPageReadonly2X";
break;
case WAIT_EVENT_DCS_TRANSFER_PAGE_FLUSHLOG:
event_name = "DcsTransferPageFlushlog";
break;
case WAIT_EVENT_DCS_TRANSFER_PAGE:
event_name = "DcsTransferPage";
break;
case WAIT_EVENT_PCR_REQ_BTREE_PAGE:
event_name = "PcrReqBtreePage";
break;
case WAIT_EVENT_PCR_REQ_HEAP_PAGE:
event_name = "PcrReqHeapPage";
break;
case WAIT_EVENT_PCR_REQ_MASTER:
event_name = "PcrReqMaster";
break;
case WAIT_EVENT_PCR_REQ_OWNER:
event_name = "PcrReqOwner";
break;
case WAIT_EVENT_PCR_CHECK_CURR_VISIBLE:
event_name = "PcrCheckCurrVisible";
break;
case WAIT_EVENT_TXN_REQ_INFO:
event_name = "TxnReqInfo";
break;
case WAIT_EVENT_TXN_REQ_SNAPSHOT:
event_name = "TxnReqSnapshot";
break;
case WAIT_EVENT_DLS_REQ_LOCK:
event_name = "DlsReqLock";
break;
case WAIT_EVENT_DLS_REQ_TABLE:
event_name = "DlsReqTable";
break;
case WAIT_EVENT_DLS_REQ_PART_X:
event_name = "DlsReqPartX";
break;
case WAIT_EVENT_DLS_REQ_PART_S:
event_name = "DlsReqPartS";
break;
case WAIT_EVENT_DLS_WAIT_TXN:
event_name = "DlsWaitTxn";
break;
case WAIT_EVENT_DEAD_LOCK_TXN:
event_name = "DeadLockTxn";
break;
case WAIT_EVENT_DEAD_LOCK_TABLE:
event_name = "DeadLockTable";
break;
case WAIT_EVENT_DEAD_LOCK_ITL:
event_name = "DeadLockItl";
break;
case WAIT_EVENT_BROADCAST_BTREE_SPLIT:
event_name = "BroadcastBtreeSplit";
break;
case WAIT_EVENT_BROADCAST_ROOT_PAGE:
event_name = "BroadcastBootPage";
break;
case WAIT_EVENT_QUERY_OWNER_ID:
event_name = "QueryOwnerId";
break;
case WAIT_EVENT_LATCH_X:
event_name = "LatchX";
break;
case WAIT_EVENT_LATCH_S:
event_name = "LatchS";
break;
case WAIT_EVENT_LATCH_X_REMOTE:
event_name = "LatchXRemote";
break;
case WAIT_EVENT_LATCH_S_REMOTE:
event_name = "LatchSRemote";
break;
case WAIT_EVENT_ONDEMAND_REDO:
event_name = "OndemandRedo";
break;
case WAIT_EVENT_PAGE_STATUS_INFO:
event_name = "PageStatusInfo";
break;
case WAIT_EVENT_OPENGAUSS_SEND_XMIN:
event_name = "OpenGaussSendXmin";
break;
case WAIT_EVENT_DCS_REQ_CREATE_XA_RES:
event_name = "DcsReqCreateXaRes";
break;
case WAIT_EVENT_DCS_REQ_DELETE_XA_RES:
event_name = "DcsReqDeleteXaRes";
break;
case WAIT_EVENT_DCS_REQ_XA_OWNER_ID:
event_name = "DcsReqXaOwnerId";
break;
case WAIT_EVENT_DCS_REQ_XA_IN_USE:
event_name = "DcsReqXaInUse";
break;
case WAIT_EVENT_DCS_REQ_END_XA:
event_name = "DcsReqEndXa";
break;
default:
event_name = "unknown wait event";
break;
}
return event_name;
}
void pgstat_report_dms_waitevent(const uint32 waitevent, const DMSWaiteventTarget* target)
{
if (IS_PGSTATE_TRACK_UNDEFINE) {
return;
}
PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry;
if (waitevent != WAIT_EVENT_END) {
if (target != NULL) {
beentry->dms_wait_target = *target;
}
pgstat_report_waitevent(waitevent);
} else {
pgstat_report_waitevent(WAIT_EVENT_END);
}
}
void decode_dms_waitevent_target(const uint32 waitevent, const DMSWaiteventTarget target,
_out_ char** object, _out_ char** mode)
{
Assert((waitevent & WAITEVENT_CLASS_MASK) == PG_WAIT_DMS);
StringInfoData res_object;
StringInfoData res_mode;
initStringInfo(&res_object);
initStringInfo(&res_mode);
if (waitevent == WAIT_EVENT_PCR_REQ_HEAP_PAGE) {
appendStringInfo(&res_object, "request page (buffer:%d)", target.page.buffer);
appendStringInfo(&res_mode, "%s", (target.page.mode == DMS_LOCK_EXCLUSIVE ? "Exclusive" : "Shared"));
} else if (waitevent == WAIT_EVENT_DCS_TRANSFER_PAGE) {
appendStringInfo(&res_object, "enter local page (buffer:%d)", target.page.buffer);
appendStringInfo(&res_mode, "%s", (target.page.mode == DMS_LOCK_EXCLUSIVE ? "Exclusive" : "Shared"));
} else if (waitevent == WAIT_EVENT_DCS_INVLDT_SHARE_COPY_REQ) {
appendStringInfo(&res_object, "invalid page (buffer:%d)", target.page.buffer);
appendStringInfo(&res_mode, "%s", (target.page.mode == DMS_LOCK_EXCLUSIVE ? "Exclusive" : "Shared"));
} else {
*object = NULL;
*mode = NULL;
FreeStringInfo(&res_object);
FreeStringInfo(&res_mode);
return;
}
*object = res_object.data;
*mode = res_mode.data;
}
* pgstat_get_current_active_numbackends() -
*
* get current active count of backends
* ----------
*/
int pgstat_get_current_active_numbackends(void)
{
int result_counter = 0;
if (t_thrd.shemem_ptr_cxt.BackendStatusArray == NULL) {
if (NULL != PgBackendStatusArray)
t_thrd.shemem_ptr_cxt.BackendStatusArray = PgBackendStatusArray;
else
return result_counter;
}
PgBackendStatusNode* node = gs_stat_read_current_status(NULL);
while (node != NULL) {
PgBackendStatus* beentry = node->data;
* If the backend thread is valid and the state
* is not idle or undefined, we treat it as a active thread.
*/
if ((beentry != NULL) && beentry->st_sessionid > 0 &&
(beentry->st_state != STATE_IDLE && beentry->st_state != STATE_UNDEFINED &&
beentry->st_state != STATE_DECOUPLED))
++result_counter;
node = node->next;
}
if (result_counter > g_instance.attr.attr_network.MaxConnections)
result_counter = g_instance.attr.attr_network.MaxConnections;
return result_counter;
}
PgBackendStatus* pgstat_get_backend_single_entry(uint64 sessionid)
{
PgBackendStatus* beentry = NULL;
int i = 0;
beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + BackendStatusArray_size - 1;
* We probably shouldn't get here before shared memory has been set up,
* but be safe.
*/
if (beentry == NULL)
return NULL;
for (i = 1; i <= BackendStatusArray_size; i++, beentry--) {
if (beentry->st_sessionid == sessionid)
return beentry;
}
return NULL;
}
* @Description: get user backend entry
* @IN userid: userid
* @Return: user data list
* @See also:
*/
List* pgstat_get_user_backend_entry(Oid userid)
{
if (t_thrd.shemem_ptr_cxt.BackendStatusArray == NULL) {
if (PgBackendStatusArray != NULL)
t_thrd.shemem_ptr_cxt.BackendStatusArray = PgBackendStatusArray;
else
return NULL;
}
List* entry_list = NULL;
PgBackendStatusNode* node = gs_stat_read_current_status(NULL);
while (node != NULL) {
PgBackendStatus* beentry = node->data;
* If the backend thread is valid and the user id
* matched, we treat it as a active thread.
*/
if (beentry != NULL) {
WLMStatistics* backstat = (WLMStatistics*)&beentry->st_backstat;
if (beentry->st_procpid <= 0 && beentry->st_sessionid == 0) {
node = node->next;
continue;
}
if (!(backstat->qtype && strcmp(backstat->qtype, "Internal") != 0)) {
node = node->next;
continue;
}
if ((backstat->status && strcmp(backstat->status, "running") == 0) ||
(backstat->enqueue && strcmp(backstat->enqueue, "Transaction") == 0) ||
(backstat->enqueue && strcmp(backstat->enqueue, "StoredProc") == 0) ||
(backstat->status && strcmp(backstat->status, "pending") == 0 && backstat->enqueue &&
(strcmp(backstat->enqueue, "None") == 0 || strcmp(backstat->enqueue, "Respool") == 0)) ||
(backstat->status && strcmp(backstat->status, "finished") == 0 && backstat->enqueue &&
strcmp(backstat->enqueue, "Respool") == 0)) {
entry_list = lappend(entry_list, beentry);
}
}
node = node->next;
}
return entry_list;
}
* @Description: get user backend entry
* @IN userid: user oid
* @OUT num: entry count
* @Return: all thread of the user
* @See also:
*/
ThreadId* pgstat_get_user_io_entry(Oid userid, int* num)
{
int idx = 0;
if (t_thrd.shemem_ptr_cxt.BackendStatusArray == NULL) {
if (NULL != PgBackendStatusArray)
t_thrd.shemem_ptr_cxt.BackendStatusArray = PgBackendStatusArray;
else
return NULL;
}
uint32 num_backends = 0;
PgBackendStatusNode* node = gs_stat_read_current_status(&num_backends);
if (num_backends == 0)
return NULL;
Assert(!u_sess->stat_cxt.pgStatRunningInCollector);
ThreadId* threads = (ThreadId*)palloc0_noexcept(num_backends * sizeof(ThreadId));
if (threads == NULL) {
pgstat_reset_current_status();
ereport(LOG, (errmsg("alloc memory failed for get user io entry.")));
return NULL;
}
while (node != NULL) {
PgBackendStatus* beentry = node->data;
* If the backend thread is valid and the io state
* is writing, we treat it as a active thread.
* For thread pool, sessions decoupled from workers are not generating data.
* Therefore, no need to count them and send signal to them.
*/
if ((beentry != NULL) && beentry->st_userid == userid && beentry->st_io_state == IOSTATE_WRITE)
threads[idx++] = beentry->st_procpid;
node = node->next;
}
*num = idx;
pgstat_reset_current_status();
return threads;
}
* @Description: get stmt tag write backend entry
* @OUT num: entry count
* @Return: all thread with st_stmttag as write
* @See also:
*/
ThreadId* pgstat_get_stmttag_write_entry(int* num)
{
int idx = 0;
if (t_thrd.shemem_ptr_cxt.BackendStatusArray == NULL) {
if (PgBackendStatusArray != NULL)
t_thrd.shemem_ptr_cxt.BackendStatusArray = PgBackendStatusArray;
else
return NULL;
}
uint32 num_backends = 0;
PgBackendStatusNode* node = gs_stat_read_current_status(&num_backends);
if (num_backends == 0)
return NULL;
Assert(!u_sess->stat_cxt.pgStatRunningInCollector);
ThreadId* threads = (ThreadId*)palloc0_noexcept(num_backends * sizeof(ThreadId));
if (threads == NULL) {
pgstat_reset_current_status();
ereport(WARNING, (errmsg("Failed to allocate memory for get io write entry.")));
return NULL;
}
while (node != NULL) {
PgBackendStatus* beentry = node->data;
node = node->next;
* If the backend thread is valid and the stmt tag
* is writing, we treat it as a active thread.
* For thread pool, sessions decoupled from workers are not generating data.
* Therefore, no need to count them and send signal to them.
*/
if (beentry != NULL) {
if (beentry->st_tid > 0 && (beentry->st_state != STATE_IDLE && beentry->st_state != STATE_UNDEFINED) &&
beentry->st_stmttag == STMTTAG_WRITE)
threads[idx++] = beentry->st_procpid;
else {
WLMStatistics* backstat = (WLMStatistics*)&beentry->st_backstat;
if (backstat->enqueue && strcmp(backstat->enqueue, "Transaction") == 0)
threads[idx++] = beentry->st_procpid;
}
}
}
*num = idx;
pgstat_reset_current_status();
return threads;
}
* @Description: get pid from status entries by application name.
* @IN num: application name
* @OUT num: entries count
* @Return: all node status which names are 'application name'
*/
PgBackendStatusNode* pgstat_get_backend_status_by_appname(const char* appName, int* resultEntryNum)
{
int idx = 0;
if (resultEntryNum != NULL) {
*resultEntryNum = 0;
}
if (t_thrd.shemem_ptr_cxt.BackendStatusArray == NULL) {
if (PgBackendStatusArray != NULL) {
t_thrd.shemem_ptr_cxt.BackendStatusArray = PgBackendStatusArray;
} else {
return NULL;
}
}
uint32 numBackends = 0;
PgBackendStatusNode* node = gs_stat_read_current_status(&numBackends);
if (numBackends == 0) {
FreeBackendStatusNodeMemory(node);
return NULL;
}
Assert(!u_sess->stat_cxt.pgStatRunningInCollector);
PgBackendStatusNode* otherNodeList = (PgBackendStatusNode*)palloc(sizeof(PgBackendStatusNode));
PgBackendStatusNode* otherNodeListHead = otherNodeList;
otherNodeList->data = NULL;
otherNodeList->next = NULL;
PgBackendStatusNode* resultNodeList = (PgBackendStatusNode*)palloc(sizeof(PgBackendStatusNode));
PgBackendStatusNode* resultNodeListHead = resultNodeList;
resultNodeList->data = NULL;
resultNodeList->next = NULL;
while (node != NULL) {
PgBackendStatus* beentry = node->data;
if (beentry != NULL) {
if (beentry->st_appname == NULL || beentry->st_tid < 0 || strcmp(beentry->st_appname, appName) != 0) {
otherNodeList->next = node;
otherNodeList = otherNodeList->next;
node = node->next;
continue;
}
resultNodeList->next = node;
resultNodeList = resultNodeList->next;
idx++;
} else {
otherNodeList->next = node;
otherNodeList = otherNodeList->next;
}
node = node->next;
}
otherNodeList->next = NULL;
resultNodeList->next = NULL;
if (resultEntryNum != NULL) {
*resultEntryNum = idx;
}
FreeBackendStatusNodeMemory(otherNodeListHead);
return resultNodeListHead;
}
* pgstat_get_backend_current_activity() -
*
* Return a string representing the current activity of the backend with
* the specified PID. This looks directly at the BackendStatusArray,
* and so will provide current information regardless of the age of our
* transaction's snapshot of the status array.
*
* It is the caller's responsibility to invoke this only for backends whose
* state is expected to remain stable while the result is in use. The
* only current use is in deadlock reporting, where we can expect that
* the target backend is blocked on a lock. (There are corner cases
* where the target's wait could get aborted while we are looking at it,
* but the very worst consequence is to return a pointer to a string
* that's been changed, so we won't worry too much.)
*
* Note: return strings for special cases match pg_stat_get_backend_activity.
* ----------
*/
const char* pgstat_get_backend_current_activity(ThreadId pid, bool checkUser)
{
PgBackendStatus* beentry = NULL;
int i;
beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + BackendStatusArray_size - 1;
for (i = 1; i <= BackendStatusArray_size; i++) {
* Although we expect the target backend's entry to be stable, that
* doesn't imply that anyone else's is. To avoid identifying the
* wrong backend, while we check for a match to the desired PID we
* must follow the protocol of retrying if st_changecount changes
* while we examine the entry, or if it's odd. (This might be
* unnecessary, since fetching or storing an int is almost certainly
* atomic, but let's play it safe.) We use a volatile pointer here to
* ensure the compiler doesn't try to get cute.
*/
volatile PgBackendStatus* vbeentry = beentry;
bool found = false;
for (;;) {
int before_changecount;
int after_changecount;
pgstat_save_changecount_before(vbeentry, before_changecount);
found = (vbeentry->st_procpid == pid);
pgstat_save_changecount_after(vbeentry, after_changecount);
if (before_changecount == after_changecount && (before_changecount & 1) == 0)
break;
CHECK_FOR_INTERRUPTS();
}
if (found) {
if (checkUser && !superuser() && beentry->st_userid != GetUserId())
return "<insufficient privilege>";
else if (*(beentry->st_activity) == '\0')
return "<command string not enabled>";
else
return beentry->st_activity;
}
beentry--;
}
return "<backend information not available>";
}
* pgstat_get_crashed_backend_activity() -
*
* Return a string representing the current activity of the backend with
* the specified PID. Like the function above, but reads shared memory with
* the expectation that it may be corrupt. On success, copy the string
* into the "buffer" argument and return that pointer. On failure,
* return NULL.
*
* This function is only intended to be used by the postmaster to report the
* query that crashed a backend. In particular, no attempt is made to
* follow the correct concurrency protocol when accessing the
* BackendStatusArray. But that's OK, in the worst case we'll return a
* corrupted message. We also must take care not to trip on ereport(ERROR).
* ----------
*/
const char* pgstat_get_crashed_backend_activity(ThreadId pid, char* buffer, int buflen)
{
volatile PgBackendStatus* beentry = NULL;
int i;
beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + BackendStatusArray_size - 1;
* We probably shouldn't get here before shared memory has been set up,
* but be safe.
*/
if (beentry == NULL || t_thrd.shemem_ptr_cxt.BackendActivityBuffer == NULL)
return NULL;
for (i = 1; i <= BackendStatusArray_size; i++) {
if (beentry->st_procpid == pid) {
const char* activity = beentry->st_activity;
const char* activity_last = NULL;
* We mustn't access activity string before we verify that it
* falls within the BackendActivityBuffer. To make sure that the
* entire string including its ending is contained within the
* buffer, subtract one activity length from the buffer size.
*/
activity_last = t_thrd.shemem_ptr_cxt.BackendActivityBuffer +
t_thrd.shemem_ptr_cxt.BackendActivityBufferSize -
g_instance.attr.attr_common.pgstat_track_activity_query_size;
if (activity < t_thrd.shemem_ptr_cxt.BackendActivityBuffer || activity > activity_last)
return NULL;
if (activity[0] == '\0')
return NULL;
* Copy only ASCII-safe characters so we don't run into encoding
* problems when reporting the message; and be sure not to run off
* the end of memory.
*/
ascii_safe_strlcpy(
buffer, activity, Min(buflen, g_instance.attr.attr_common.pgstat_track_activity_query_size));
return buffer;
}
beentry--;
}
return NULL;
}
* Send SIGINT to those backends of which the running queries
* were started with a GTM that has broken down or been demoted.
*
* At present, this function is called automatically by twophasecleaner
* every gtm_conn_validation_interval. It can also be called manually with
* the built-in pg_cancel_invalid_query function, only by super-user.
*/
void pgstat_cancel_invalid_gtm_conn(void)
{
volatile PgBackendStatus* beentry = NULL;
bool CalledByBackend = false;
volatile GtmHostIndex hostindex = GTM_HOST_INVAILD;
GTM_Timeline txnTimeline = InvalidTransactionTimeline;
if (GTM_FREE_MODE)
return;
beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray;
* Register the host index and the timeline of the currently
* running GTM to MyBEEntry.
* If no GTM is running, both fields are set to be 0.
*/
InitGTM_Reporttimeline();
* Compared with the newly obtained GTM host index or timeline,
* those backends having different entries in the BackendStatusArray
* must be connected to a demoted or broken down GTM. So
* send SIGINT to them.
*/
for (int i = 1; i <= BackendStatusArray_size; i++, beentry++) {
SpinLockAcquire(&beentry->use_mutex);
hostindex = beentry->st_gtmhost;
txnTimeline = beentry->st_gtmtimeline;
SpinLockRelease(&beentry->use_mutex);
* Skip unstarted or unconnected gtm backend.
* Under thread pool mode, no need to cancel decoupled sessions.
*/
if (beentry->st_procpid == 0 || hostindex == GTM_HOST_INVAILD)
continue;
if (hostindex != t_thrd.shemem_ptr_cxt.MyBEEntry->st_gtmhost ||
txnTimeline != t_thrd.shemem_ptr_cxt.MyBEEntry->st_gtmtimeline) {
if (gs_signal_send(beentry->st_procpid, SIGUSR2))
ereport(WARNING, (errmsg("could not send signal to thread %lu: %m", beentry->st_procpid)));
else
ereport(LOG,
(errmsg("Success to send SIGUSR2 to openGauss thread: %lu in "
"pgstat_cancel_invalid_gtm_conn",
beentry->st_procpid)));
}
if (beentry->st_procpid == t_thrd.shemem_ptr_cxt.MyBEEntry->st_procpid)
CalledByBackend = true;
}
* If this function is called by a backend, leave
* the GTM connection unclosed for future transaction.
*/
if (!CalledByBackend)
CloseGTM();
}
* Local support functions follow
* ------------------------------------------------------------
*/
* pgstat_setheader() -
*
* Set common header fields in a statistics message
* ----------
*/
static void pgstat_setheader(PgStat_MsgHdr* hdr, StatMsgType mtype)
{
hdr->m_type = mtype;
}
* pgstat_send() -
*
* Send out one statistics message to the collector
* ----------
*/
void pgstat_send(void* msg, int len)
{
int rc;
if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET)
return;
((PgStat_MsgHdr*)msg)->m_size = len;
do {
PGSTAT_INIT_TIME_RECORD();
PGSTAT_START_TIME_RECORD();
rc = send(g_instance.stat_cxt.pgStatSock, msg, len, 0);
END_NET_SEND_INFO(rc);
} while (rc < 0 && errno == EINTR);
#ifdef USE_ASSERT_CHECKING
if (rc < 0)
elog(LOG, "could not send to statistics collector: %m");
#endif
}
* pgstat_send_bgwriter() -
*
* Send bgwriter statistics to the collector
* ----------
*/
void pgstat_send_bgwriter(void)
{
static const PgStat_MsgBgWriter all_zeroes = {{PGSTAT_MTYPE_DUMMY}};
* 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(u_sess->stat_cxt.BgWriterStats, &all_zeroes, sizeof(PgStat_MsgBgWriter)) == 0)
return;
* Prepare and send the message
*/
pgstat_setheader(&u_sess->stat_cxt.BgWriterStats->m_hdr, PGSTAT_MTYPE_BGWRITER);
pgstat_send(u_sess->stat_cxt.BgWriterStats, sizeof(PgStat_MsgBgWriter));
* Clear out the statistics buffer, so it can be re-used.
*/
errno_t rc = memset_s(u_sess->stat_cxt.BgWriterStats, sizeof(PgStat_MsgBgWriter), 0, sizeof(PgStat_MsgBgWriter));
securec_check(rc, "\0", "\0");
}
static void PgstatCollectThreadStatus(void)
{
pgstat_collect_thread_status_setup_memcxt();
PgBackendStatusNode* node = gs_stat_read_current_status(NULL);
while (node != NULL) {
PgBackendStatus* beentry = node->data;
node = node->next;
char* wait_status = NULL;
if (NULL == beentry)
continue;
if (STATE_WAIT_UNDEFINED == beentry->st_waitstatus || STATE_WAIT_COMM == beentry->st_waitstatus)
continue;
wait_status = getThreadWaitStatusDesc(beentry);
elog(LOG,
"PgstatCollectThreadStatus, node_name<%s>, datid<%u>, app_name<%s>, "
"query_id<%lu>, tid<%lu>, lwtid<%d>, parent_sessionid<%lu>, thread_level<%d>, wait_status<%s>",
g_instance.attr.attr_common.PGXCNodeName,
beentry->st_databaseid,
beentry->st_appname ? beentry->st_appname : "unnamed thread",
beentry->st_queryid,
beentry->st_procpid,
beentry->st_tid,
beentry->st_parent_sessionid,
beentry->st_thread_level,
wait_status);
pfree(wait_status);
}
pgstat_collect_thread_status_clear_resource();
}
* PgstatCollectorMain() -
*
* Start up the statistics collector process. This is the body of the
* postmaster child process.
*
* The argc/argv parameters are valid only in EXEC_BACKEND case.
* ----------
*/
void PgstatCollectorMain()
{
int len;
PgStat_Msg msg;
int wr;
TimestampTz get_thread_status_start_time;
IsUnderPostmaster = true;
t_thrd.proc_cxt.MyProcPid = gs_thread_self();
t_thrd.proc_cxt.MyStartTime = time(NULL);
t_thrd.proc_cxt.MyProgName = "PgstatCollector";
t_thrd.myLogicTid = noProcLogicTid + PGSTAT_LID;
InitLatch(&g_instance.stat_cxt.pgStatLatch);
* Ignore all signals usually bound to some action in the postmaster,
* except SIGHUP and SIGQUIT. Note we don't need a SIGUSR1 handler to
* support latch operations, because g_instance.stat_cxt.pgStatLatch is local not shared.
*/
(void)gspqsignal(SIGURG, print_stack);
(void)gspqsignal(SIGHUP, pgstat_sighup_handler);
(void)gspqsignal(SIGINT, SIG_IGN);
(void)gspqsignal(SIGTERM, SIG_IGN);
(void)gspqsignal(SIGQUIT, pgstat_exit);
(void)gspqsignal(SIGALRM, SIG_IGN);
(void)gspqsignal(SIGPIPE, SIG_IGN);
(void)gspqsignal(SIGUSR1, SIG_IGN);
(void)gspqsignal(SIGUSR2, SIG_IGN);
(void)gspqsignal(SIGCHLD, SIG_DFL);
(void)gspqsignal(SIGTTIN, SIG_DFL);
(void)gspqsignal(SIGTTOU, SIG_DFL);
(void)gspqsignal(SIGCONT, SIG_DFL);
(void)gspqsignal(SIGWINCH, SIG_DFL);
gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);
(void)gs_signal_unblock_sigusr2();
* Identify myself via ps
*/
init_ps_display("stats collector process", "", "", "");
* Arrange to write the initial status file right away
*/
get_thread_status_start_time = g_instance.stat_cxt.last_statrequest = GetCurrentTimestamp();
g_instance.stat_cxt.last_statwrite = g_instance.stat_cxt.last_statrequest - 1;
* Read in an existing statistics stats file or initialize the stats to
* zero.
*/
u_sess->stat_cxt.pgStatRunningInCollector = true;
u_sess->stat_cxt.pgStatDBHash = pgstat_read_statsfile(InvalidOid, true);
t_thrd.mem_cxt.mask_password_mem_cxt = AllocSetContextCreate(t_thrd.top_mem_cxt,
"MaskPasswordCtx",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
* Loop to process messages until we get SIGQUIT or detect ungraceful
* death of our parent postmaster.
*
* For performance reasons, we don't want to do ResetLatch/WaitLatch after
* every message; instead, do that only after a recv() fails to obtain a
* message. (This effectively means that if backends are sending us stuff
* like mad, we won't notice postmaster death until things slack off a
* bit; which seems fine.) To do that, we have an inner loop that
* iterates as long as recv() succeeds. We do recognize got_SIGHUP inside
* the inner loop, which means that such interrupts will get serviced but
* the latch won't get cleared until next time there is a break in the
* action.
*/
for (;;) {
TimestampTz get_thread_status_current_time = GetCurrentTimestamp();
if (u_sess->attr.attr_common.pgstat_collect_thread_status_interval > 0 &&
TimestampDifferenceExceeds(get_thread_status_start_time,
get_thread_status_current_time,
60 * u_sess->attr.attr_common.pgstat_collect_thread_status_interval * 1000)) {
PgstatCollectThreadStatus();
get_thread_status_start_time = GetCurrentTimestamp();
}
ResetLatch(&g_instance.stat_cxt.pgStatLatch);
* Quit if we get SIGQUIT from the postmaster.
*/
if (t_thrd.stat_cxt.need_exit)
break;
* Inner loop iterates as long as we keep getting messages, or until
* need_exit becomes set.
*/
while (!t_thrd.stat_cxt.need_exit) {
* Reload configuration if we got SIGHUP from the postmaster.
*/
if (g_instance.stat_cxt.got_SIGHUP) {
g_instance.stat_cxt.got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
}
* Write the stats file if a new request has arrived that is not
* satisfied by existing file.
*/
if (g_instance.stat_cxt.last_statwrite < g_instance.stat_cxt.last_statrequest)
pgstat_write_statsfile(false);
PgstatUpdateHotkeys();
* Try to receive and process a message. This will not block,
* since the socket is set to non-blocking mode.
*
* XXX On Windows, we have to force pgwin32_recv to cooperate,
* despite the previous use of pg_set_noblock() on the socket.
* This is extremely broken and should be fixed someday.
*/
#ifdef WIN32
pgwin32_noblock = 1;
#endif
len = recv(g_instance.stat_cxt.pgStatSock, (char*)&msg, sizeof(PgStat_Msg), 0);
#ifdef WIN32
pgwin32_noblock = 0;
#endif
if (len < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
break;
ereport(ERROR, (errcode_for_socket_access(), errmsg("could not read statistics message: %m")));
}
* We ignore messages that are smaller than our common header
*/
if ((size_t)len < sizeof(PgStat_MsgHdr))
continue;
* The received length must match the length in the header
*/
if (msg.msg_hdr.m_size != len)
continue;
* O.K. - we accept this message. Process it.
*/
switch (msg.msg_hdr.m_type) {
case PGSTAT_MTYPE_DUMMY:
break;
case PGSTAT_MTYPE_INQUIRY:
pgstat_recv_inquiry((PgStat_MsgInquiry*)&msg, len);
break;
case PGSTAT_MTYPE_TABSTAT:
pgstat_recv_tabstat((PgStat_MsgTabstat*)&msg, len);
break;
case PGSTAT_MTYPE_TABPURGE:
pgstat_recv_tabpurge((PgStat_MsgTabpurge*)&msg, len);
break;
case PGSTAT_MTYPE_DROPDB:
pgstat_recv_dropdb((PgStat_MsgDropdb*)&msg, len);
break;
case PGSTAT_MTYPE_RESETCOUNTER:
pgstat_recv_resetcounter((PgStat_MsgResetcounter*)&msg, len);
break;
case PGSTAT_MTYPE_RESETSHAREDCOUNTER:
pgstat_recv_resetsharedcounter((PgStat_MsgResetsharedcounter*)&msg, len);
break;
case PGSTAT_MTYPE_RESETSINGLECOUNTER:
pgstat_recv_resetsinglecounter((PgStat_MsgResetsinglecounter*)&msg, len);
break;
case PGSTAT_MTYPE_AUTOVAC_START:
pgstat_recv_autovac((PgStat_MsgAutovacStart*)&msg, len);
break;
case PGSTAT_MTYPE_VACUUM:
pgstat_recv_vacuum((PgStat_MsgVacuum*)&msg, len);
break;
case PGSTAT_MTYPE_AUTOVAC_STAT:
pgstat_recv_autovac_stat((PgStat_MsgAutovacStat*)&msg, len);
break;
case PGSTAT_MTYPE_DATA_CHANGED:
pgstat_recv_data_changed((PgStat_MsgDataChanged*)&msg, len);
break;
case PGSTAT_MTYPE_TRUNCATE:
pgstat_recv_truncate((PgStat_MsgTruncate*)&msg, len);
break;
case PGSTAT_MTYPE_ANALYZE:
pgstat_recv_analyze((PgStat_MsgAnalyze*)&msg, len);
break;
case PGSTAT_MTYPE_BGWRITER:
pgstat_recv_bgwriter((PgStat_MsgBgWriter*)&msg, len);
break;
case PGSTAT_MTYPE_FUNCSTAT:
pgstat_recv_funcstat((PgStat_MsgFuncstat*)&msg, len);
break;
case PGSTAT_MTYPE_FUNCPURGE:
pgstat_recv_funcpurge((PgStat_MsgFuncpurge*)&msg, len);
break;
case PGSTAT_MTYPE_RECOVERYCONFLICT:
pgstat_recv_recoveryconflict((PgStat_MsgRecoveryConflict*)&msg);
break;
case PGSTAT_MTYPE_DEADLOCK:
pgstat_recv_deadlock((PgStat_MsgDeadlock*)&msg);
break;
case PGSTAT_MTYPE_FILE:
pgstat_recv_filestat((PgStat_MsgFile*)&msg, len);
break;
case PGSTAT_MTYPE_TEMPFILE:
pgstat_recv_tempfile((PgStat_MsgTempFile*)&msg, len);
break;
case PGSTAT_MTYPE_MEMRESERVED:
pgstat_recv_memReserved((PgStat_MsgMemReserved*)&msg);
break;
case PGSTAT_MTYPE_BADBLOCK:
pgstat_recv_badblock_stat((PgStat_MsgBadBlock*)&msg, len);
break;
case PGSTAT_MTYPE_RESPONSETIME:
pgstat_recv_sql_responstime((PgStat_SqlRT*)&msg, len);
break;
case PGSTAT_MTYPE_CLEANUPHOTKEYS:
pgstat_recv_cleanup_hotkeys((PgStat_MsgCleanupHotkeys*)&msg, len);
break;
case PGSTAT_MTYPE_PRUNESTAT:
PgstatRecvPrunestat((PgStat_MsgPrune*)&msg, len);
break;
default:
break;
}
}
#ifndef WIN32
wr = WaitLatchOrSocket(&g_instance.stat_cxt.pgStatLatch,
WL_LATCH_SET | WL_POSTMASTER_DEATH | WL_SOCKET_READABLE | WL_TIMEOUT,
g_instance.stat_cxt.pgStatSock,
60 * 1000L );
#else
* Windows, at least in its Windows Server 2003 R2 incarnation,
* sometimes loses FD_READ events. Waking up and retrying the recv()
* fixes that, so don't sleep indefinitely. This is a crock of the
* first water, but until somebody wants to debug exactly what's
* happening there, this is the best we can do. The two-second
* timeout matches our pre-9.2 behavior, and needs to be short enough
* to not provoke "using stale statistics" complaints from
* backend_read_statsfile.
*/
wr = WaitLatchOrSocket(&g_instance.stat_cxt.pgStatLatch,
WL_LATCH_SET | WL_POSTMASTER_DEATH | WL_SOCKET_READABLE | WL_TIMEOUT,
g_instance.stat_cxt.pgStatSock,
2 * 1000L );
#endif
* Emergency bailout if postmaster has died. This is to avoid the
* necessity for manual cleanup of all postmaster children.
*/
if ((uint32)wr & WL_POSTMASTER_DEATH)
break;
}
* Save the final stats to reuse at next startup.
*/
pgstat_write_statsfile(true);
DEC_NUM_ALIVE_THREADS_WAITTED();
gs_thread_exit(0);
}
static void pgstat_exit(SIGNAL_ARGS)
{
int save_errno = errno;
t_thrd.stat_cxt.need_exit = true;
SetLatch(&g_instance.stat_cxt.pgStatLatch);
errno = save_errno;
}
static void pgstat_sighup_handler(SIGNAL_ARGS)
{
int save_errno = errno;
g_instance.stat_cxt.got_SIGHUP = true;
SetLatch(&g_instance.stat_cxt.pgStatLatch);
errno = save_errno;
}
* Lookup the hash table entry for the specified database. If no hash
* table entry exists, initialize it, if the create parameter is true.
* Else, return NULL.
*/
static PgStat_StatDBEntry* pgstat_get_db_entry(Oid databaseid, bool create)
{
PgStat_StatDBEntry* result = NULL;
bool found = false;
HASHACTION action = (create ? HASH_ENTER : HASH_FIND);
errno_t rc = EOK;
result = (PgStat_StatDBEntry*)hash_search(u_sess->stat_cxt.pgStatDBHash, &databaseid, action, &found);
if (!create && !found)
return NULL;
if (!found) {
HASHCTL hash_ctl;
result->tables = NULL;
result->functions = NULL;
result->n_xact_commit = 0;
result->n_xact_rollback = 0;
result->n_blocks_fetched = 0;
result->n_blocks_hit = 0;
result->n_cu_mem_hit = 0;
result->n_cu_hdd_sync = 0;
result->n_cu_hdd_asyn = 0;
result->n_tuples_returned = 0;
result->n_tuples_fetched = 0;
result->n_tuples_inserted = 0;
result->n_tuples_updated = 0;
result->n_tuples_deleted = 0;
result->last_autovac_time = 0;
result->n_conflict_tablespace = 0;
result->n_conflict_lock = 0;
result->n_conflict_snapshot = 0;
result->n_conflict_bufferpin = 0;
result->n_conflict_startup_deadlock = 0;
result->n_temp_files = 0;
result->n_temp_bytes = 0;
result->n_deadlocks = 0;
result->n_block_read_time = 0;
result->n_block_write_time = 0;
result->n_mem_mbytes_reserved = 0;
result->stat_reset_timestamp = GetCurrentTimestamp();
rc = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl));
securec_check(rc, "\0", "\0");
hash_ctl.keysize = sizeof(PgStat_StatTabKey);
hash_ctl.entrysize = sizeof(PgStat_StatTabEntry);
hash_ctl.hash = tag_hash;
result->tables = hash_create("Per-database table", PGSTAT_TAB_HASH_SIZE, &hash_ctl, HASH_ELEM | HASH_FUNCTION);
hash_ctl.keysize = sizeof(Oid);
hash_ctl.entrysize = sizeof(PgStat_StatFuncEntry);
hash_ctl.hash = oid_hash;
result->functions =
hash_create("Per-database function", PGSTAT_FUNCTION_HASH_SIZE, &hash_ctl, HASH_ELEM | HASH_FUNCTION);
}
return result;
}
* Lookup the hash table entry for the specified table. If no hash
* table entry exists, initialize it, if the create parameter is true.
* Else, return NULL.
*/
static PgStat_StatTabEntry* pgstat_get_tab_entry(
PgStat_StatDBEntry* dbentry, Oid tableoid, bool create, uint32 statFlag)
{
PgStat_StatTabEntry* result = NULL;
bool found = false;
HASHACTION action = (create ? HASH_ENTER : HASH_FIND);
PgStat_StatTabKey tabkey;
tabkey.statFlag = statFlag;
tabkey.tableid = tableoid;
result = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)(&tabkey), action, &found);
if (!create && !found)
return NULL;
if (!found) {
result->numscans = 0;
result->tuples_returned = 0;
result->tuples_fetched = 0;
result->tuples_inserted = 0;
result->tuples_updated = 0;
result->tuples_deleted = 0;
result->tuples_hot_updated = 0;
result->tuples_inplace_updated = 0;
result->n_live_tuples = 0;
result->n_dead_tuples = 0;
result->changes_since_analyze = 0;
result->blocks_fetched = 0;
result->blocks_hit = 0;
result->cu_mem_hit = 0;
result->cu_hdd_sync = 0;
result->cu_hdd_asyn = 0;
result->vacuum_timestamp = 0;
result->vacuum_count = 0;
result->autovac_vacuum_timestamp = 0;
result->autovac_vacuum_count = 0;
result->analyze_timestamp = 0;
result->analyze_count = 0;
result->autovac_analyze_timestamp = 0;
result->autovac_analyze_count = 0;
result->autovac_status = 0;
result->data_changed_timestamp = 0;
result->success_prune_cnt = 0;
result->total_prune_cnt = 0;
}
return result;
}
* pgstat_write_statsfile() -
*
* Tell the news.
* If writing to the permanent file (happens when the collector is
* shutting down only), remove the temporary file so that backends
* starting up under a new postmaster can't read the old data before
* the new collector is ready.
* ----------
*/
static void pgstat_write_statsfile(bool permanent)
{
HASH_SEQ_STATUS hstat;
HASH_SEQ_STATUS tstat;
HASH_SEQ_STATUS fstat;
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatTabEntry* tabentry = NULL;
PgStat_StatFuncEntry* funcentry = NULL;
FILE* fpout = NULL;
int32 format_id;
const char* tmpfile = permanent ? PGSTAT_STAT_PERMANENT_TMPFILE : u_sess->stat_cxt.pgstat_stat_tmpname;
const char* statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : u_sess->stat_cxt.pgstat_stat_filename;
int rc;
* Open the statistics temp file to write out the current values.
*/
fpout = AllocateFile(tmpfile, PG_BINARY_W);
if (fpout == NULL) {
ereport(
LOG, (errcode_for_file_access(), errmsg("could not open temporary statistics file \"%s\": %m", tmpfile)));
return;
}
* Set the timestamp of the stats file.
*/
u_sess->stat_cxt.globalStats->stats_timestamp = GetCurrentTimestamp();
* Write the file header --- currently just a format ID.
*/
format_id = PGSTAT_FILE_FORMAT_ID;
rc = fwrite(&format_id, sizeof(format_id), 1, fpout);
(void)rc;
* Write global stats struct
*/
rc = fwrite(u_sess->stat_cxt.globalStats, sizeof(PgStat_GlobalStats), 1, fpout);
(void)rc;
* Walk through the database table.
*/
hash_seq_init(&hstat, u_sess->stat_cxt.pgStatDBHash);
while ((dbentry = (PgStat_StatDBEntry*)hash_seq_search(&hstat)) != NULL) {
* Write out the DB entry including the number of live backends. We
* don't write the tables or functions pointers, since they're of no
* use to any other process.
*/
fputc('D', fpout);
rc = fwrite(dbentry, offsetof(PgStat_StatDBEntry, tables), 1, fpout);
(void)rc;
* Walk through the database's access stats per table.
*/
hash_seq_init(&tstat, dbentry->tables);
while ((tabentry = (PgStat_StatTabEntry*)hash_seq_search(&tstat)) != NULL) {
fputc('T', fpout);
rc = fwrite(tabentry, sizeof(PgStat_StatTabEntry), 1, fpout);
(void)rc;
}
* Walk through the database's function stats table.
*/
hash_seq_init(&fstat, dbentry->functions);
while ((funcentry = (PgStat_StatFuncEntry*)hash_seq_search(&fstat)) != NULL) {
fputc('F', fpout);
rc = fwrite(funcentry, sizeof(PgStat_StatFuncEntry), 1, fpout);
(void)rc;
}
* Mark the end of this DB
*/
fputc('d', fpout);
}
if (g_instance.repair_cxt.global_repair_bad_block_stat != NULL) {
LWLockAcquire(RepairBadBlockStatHashLock, LW_SHARED);
HASH_SEQ_STATUS repairStat;
BadBlockEntry* repairEntry;
hash_seq_init(&repairStat, g_instance.repair_cxt.global_repair_bad_block_stat);
while ((repairEntry = (BadBlockEntry*)hash_seq_search(&repairStat)) != NULL) {
if (repairEntry->repair_time != -1) {
continue;
}
fputc('R', fpout);
rc = fwrite(repairEntry, sizeof(BadBlockEntry), 1, fpout);
(void)rc;
}
LWLockRelease(RepairBadBlockStatHashLock);
fputc('r', fpout);
}
* No more output to be done. Close the temp file and replace the old
* pgstat.stat with it. The ferror() check replaces testing for error
* after each individual fputc or fwrite above.
*/
fputc('E', fpout);
if (ferror(fpout)) {
ereport(
LOG, (errcode_for_file_access(), errmsg("could not write temporary statistics file \"%s\": %m", tmpfile)));
FreeFile(fpout);
unlink(tmpfile);
} else if (FreeFile(fpout) < 0) {
ereport(
LOG, (errcode_for_file_access(), errmsg("could not close temporary statistics file \"%s\": %m", tmpfile)));
unlink(tmpfile);
} else if (rename(tmpfile, statfile) < 0) {
ereport(LOG,
(errcode_for_file_access(),
errmsg("could not rename temporary statistics file \"%s\" to \"%s\": %m", tmpfile, statfile)));
unlink(tmpfile);
} else {
* Successful write, so update g_instance.stat_cxt.last_statwrite.
*/
g_instance.stat_cxt.last_statwrite = u_sess->stat_cxt.globalStats->stats_timestamp;
* If there is clock skew between backends and the collector, we could
* receive a stats request time that's in the future. If so, complain
* and reset g_instance.stat_cxt.last_statrequest. Resetting ensures that no inquiry
* message can cause more than one stats file write to occur.
*/
if (g_instance.stat_cxt.last_statrequest > g_instance.stat_cxt.last_statwrite) {
char* reqtime = NULL;
char* mytime = NULL;
reqtime = pstrdup(timestamptz_to_str(g_instance.stat_cxt.last_statrequest));
mytime = pstrdup(timestamptz_to_str(g_instance.stat_cxt.last_statwrite));
elog(LOG, "g_instance.stat_cxt.last_statrequest %s is later than collector's time %s", reqtime, mytime);
pfree(reqtime);
pfree(mytime);
g_instance.stat_cxt.last_statrequest = g_instance.stat_cxt.last_statwrite;
}
}
if (permanent)
unlink(u_sess->stat_cxt.pgstat_stat_filename);
}
static HTAB* pgstat_read_statsfile_hashcreate(const char* tableString, int hashSize, HASHCTL* hashCtl)
{
HTAB* hashTable;
if (IsGlobalStatsTrackerProcess()) {
hashTable = hash_create(tableString, hashSize, hashCtl, HASH_ELEM | HASH_FUNCTION | HASH_SHRCTX);
} else {
hashTable = hash_create(tableString, hashSize, hashCtl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
}
return hashTable;
}
* pgstat_read_statsfile() -
*
* Reads in an existing statistics collector file and initializes the
* databases' hash table (whose entries point to the tables' hash tables).
* ----------
*/
static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent)
{
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatDBEntry dbbuf;
PgStat_StatTabEntry* tabentry = NULL;
PgStat_StatTabEntry tabbuf;
PgStat_StatFuncEntry funcbuf;
PgStat_StatFuncEntry* funcentry = NULL;
BadBlockEntry* repairEntry = NULL;
BadBlockEntry repairBuf;
HASHCTL hash_ctl;
HTAB* dbhash = NULL;
HTAB* tabhash = NULL;
HTAB* funchash = NULL;
FILE* fpin = NULL;
int32 format_id;
bool found = false;
const char* statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : u_sess->stat_cxt.pgstat_stat_filename;
errno_t rc = EOK;
* The tables will live in u_sess->stat_cxt.pgStatLocalContext.
*/
pgstat_setup_memcxt();
* Create the DB hashtable
*/
rc = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl));
securec_check(rc, "\0", "\0");
hash_ctl.keysize = sizeof(Oid);
hash_ctl.entrysize = sizeof(PgStat_StatDBEntry);
hash_ctl.hash = oid_hash;
hash_ctl.hcxt = u_sess->stat_cxt.pgStatLocalContext;
dbhash = pgstat_read_statsfile_hashcreate("Databases hash", PGSTAT_DB_HASH_SIZE, &hash_ctl);
* Clear out global statistics so they start from zero in case we can't
* load an existing statsfile.
*/
rc = memset_s(u_sess->stat_cxt.globalStats, sizeof(PgStat_GlobalStats), 0, sizeof(PgStat_GlobalStats));
securec_check(rc, "\0", "\0");
* Set the current timestamp (will be kept only in case we can't load an
* existing statsfile.
*/
u_sess->stat_cxt.globalStats->stat_reset_timestamp = GetCurrentTimestamp();
* 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.
*
* ENOENT is a possibility if the stats collector is not running or has
* not yet written the stats file the first time. Any other failure
* condition is suspicious.
*/
if ((fpin = AllocateFile(statfile, PG_BINARY_R)) == NULL) {
if (errno != ENOENT)
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errcode_for_file_access(), errmsg("could not open statistics file \"%s\": %m", statfile)));
elog(LOG, "[Pgstat] statfile %s is missing, using empty dbhash.", statfile);
return dbhash;
}
* Verify it's of the expected format.
*/
if (fread(&format_id, 1, sizeof(format_id), fpin) != sizeof(format_id) || format_id != PGSTAT_FILE_FORMAT_ID) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
* Read global stats struct
*/
if (fread(u_sess->stat_cxt.globalStats, 1, sizeof(PgStat_GlobalStats), fpin) != sizeof(PgStat_GlobalStats)) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
* 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' and 'F' entries
* will follow until a 'd' is encountered.
*/
case 'D':
if (fread(&dbbuf, 1, offsetof(PgStat_StatDBEntry, tables), fpin) !=
offsetof(PgStat_StatDBEntry, tables)) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
* Add to the DB hash
*/
dbentry = (PgStat_StatDBEntry*)hash_search(dbhash, (void*)&dbbuf.databaseid, HASH_ENTER, &found);
if (found) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
rc = memcpy_s(dbentry, sizeof(PgStat_StatDBEntry), &dbbuf, sizeof(PgStat_StatDBEntry));
securec_check(rc, "", "");
dbentry->tables = NULL;
dbentry->functions = NULL;
* Don't collect tables if not the requested DB (or the
* shared-table info)
*/
if (onlydb != InvalidOid) {
if (dbbuf.databaseid != onlydb && dbbuf.databaseid != InvalidOid)
break;
}
rc = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl));
securec_check(rc, "\0", "\0");
hash_ctl.keysize = sizeof(PgStat_StatTabKey);
hash_ctl.entrysize = sizeof(PgStat_StatTabEntry);
hash_ctl.hash = tag_hash;
hash_ctl.hcxt = u_sess->stat_cxt.pgStatLocalContext;
dbentry->tables = pgstat_read_statsfile_hashcreate("Per-database table",
PGSTAT_TAB_HASH_SIZE, &hash_ctl);
hash_ctl.keysize = sizeof(Oid);
hash_ctl.entrysize = sizeof(PgStat_StatFuncEntry);
hash_ctl.hash = oid_hash;
hash_ctl.hcxt = u_sess->stat_cxt.pgStatLocalContext;
dbentry->functions = pgstat_read_statsfile_hashcreate("Per-database function",
PGSTAT_FUNCTION_HASH_SIZE, &hash_ctl);
* Arrange that following records add entries to this
* database's hash tables.
*/
tabhash = dbentry->tables;
funchash = dbentry->functions;
break;
* 'd' End of this database.
*/
case 'd':
tabhash = NULL;
funchash = NULL;
break;
* 'T' A PgStat_StatTabEntry follows.
*/
case 'T':
if (fread(&tabbuf, 1, sizeof(PgStat_StatTabEntry), fpin) != sizeof(PgStat_StatTabEntry)) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
* Skip if table belongs to a not requested database.
*/
if (tabhash == NULL)
break;
tabentry = (PgStat_StatTabEntry*)hash_search(tabhash, (void*)&(tabbuf.tablekey), HASH_ENTER, &found);
if (found) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
rc = memcpy_s(tabentry, sizeof(PgStat_StatTabEntry), &tabbuf, sizeof(tabbuf));
securec_check(rc, "", "");
break;
* 'F' A PgStat_StatFuncEntry follows.
*/
case 'F':
if (fread(&funcbuf, 1, sizeof(PgStat_StatFuncEntry), fpin) != sizeof(PgStat_StatFuncEntry)) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
* Skip if function belongs to a not requested database.
*/
if (funchash == NULL)
break;
funcentry =
(PgStat_StatFuncEntry*)hash_search(funchash, (void*)&funcbuf.functionid, HASH_ENTER, &found);
if (found) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
rc = memcpy_s(funcentry, sizeof(PgStat_StatFuncEntry), &funcbuf, sizeof(funcbuf));
securec_check(rc, "", "");
break;
case 'R':
if (g_instance.repair_cxt.global_repair_bad_block_stat == NULL) {
break;
}
if (fread(&repairBuf, 1, sizeof(BadBlockEntry), fpin) != sizeof(BadBlockEntry)) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
* Add to the DB hash
*/
repairEntry = (BadBlockEntry*)hash_search(g_instance.repair_cxt.global_repair_bad_block_stat,
(void*)&(repairBuf.key), HASH_ENTER, &found);
if (found) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
}
rc = memcpy_s(repairEntry, sizeof(BadBlockEntry), &repairBuf, sizeof(BadBlockEntry));
securec_check(rc, "", "");
break;
case 'r':
break;
* 'E' The EOF marker of a complete stats file.
*/
case 'E':
goto done;
default:
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
}
done:
(void)FreeFile(fpin);
if (permanent)
unlink(PGSTAT_STAT_PERMANENT_FILENAME);
return dbhash;
}
* pgstat_read_statsfile_timestamp() -
*
* Attempt to fetch the timestamp of an existing stats file.
* Returns TRUE if successful (timestamp is stored at *ts).
* ----------
*/
static bool pgstat_read_statsfile_timestamp(bool permanent, TimestampTz* ts)
{
PgStat_GlobalStats myGlobalStats;
FILE* fpin = NULL;
int32 format_id;
const char* statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : u_sess->stat_cxt.pgstat_stat_filename;
* Try to open the status file. As above, anything but ENOENT is worthy
* of complaining about.
*/
if ((fpin = AllocateFile(statfile, PG_BINARY_R)) == NULL) {
if (errno != ENOENT)
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errcode_for_file_access(), errmsg("could not open statistics file \"%s\": %m", statfile)));
return false;
}
* Verify it's of the expected format.
*/
if (fread(&format_id, 1, sizeof(format_id), fpin) != sizeof(format_id) || format_id != PGSTAT_FILE_FORMAT_ID) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
(void)FreeFile(fpin);
return false;
}
* Read global stats struct
*/
if (fread(&myGlobalStats, 1, sizeof(myGlobalStats), fpin) != sizeof(myGlobalStats)) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
(void)FreeFile(fpin);
return false;
}
*ts = myGlobalStats.stats_timestamp;
(void)FreeFile(fpin);
return true;
}
* If not already done, read the statistics collector stats file into
* some hash tables. The results will be kept until pgstat_clear_snapshot()
* is called (typically, at end of transaction).
*/
static void backend_read_statsfile(void)
{
TimestampTz cur_ts;
TimestampTz min_ts;
int count;
if (u_sess->stat_cxt.pgStatDBHash)
return;
Assert(!u_sess->stat_cxt.pgStatRunningInCollector);
* We set the minimum acceptable timestamp to PGSTAT_STAT_INTERVAL msec
* before now. This indirectly ensures that the collector needn't write
* the file more often than PGSTAT_STAT_INTERVAL. In an autovacuum
* worker, however, we want a lower delay to avoid using stale data, so we
* use PGSTAT_RETRY_DELAY (since the number of worker is low, this
* shouldn't be a problem).
*
* Note that we don't recompute min_ts after sleeping; so we might end up
* accepting a file a bit older than PGSTAT_STAT_INTERVAL. In practice
* that shouldn't happen, though, as long as the sleep time is less than
* PGSTAT_STAT_INTERVAL; and we don't want to lie to the collector about
* what our cutoff time really is.
*/
cur_ts = GetCurrentTimestamp();
if (IsAutoVacuumWorkerProcess())
min_ts = TimestampTzPlusMilliseconds(cur_ts, -PGSTAT_RETRY_DELAY);
else
min_ts = TimestampTzPlusMilliseconds(cur_ts, -PGSTAT_STAT_INTERVAL);
* Loop until fresh enough stats file is available or we ran out of time.
* The stats inquiry message is sent repeatedly in case collector drops
* it; but not every single time, as that just swamps the collector.
*/
for (count = 0; count < PGSTAT_POLL_LOOP_COUNT; count++) {
TimestampTz file_ts = 0;
CHECK_FOR_INTERRUPTS();
if (pgstat_read_statsfile_timestamp(false, &file_ts) && file_ts >= min_ts)
break;
if (g_instance.pid_cxt.PgStatPID == 0) {
ereport(LOG, (errmsg("statistics collector process is not exists using stale statistics")));
break;
}
if ((count % PGSTAT_INQ_LOOP_COUNT) == 0)
pgstat_send_inquiry(min_ts);
pg_usleep(PGSTAT_RETRY_DELAY * 1000L);
}
if (count >= PGSTAT_POLL_LOOP_COUNT)
ereport(LOG,
(errmsg("using stale statistics instead of current ones "
"because stats collector is not responding")));
if (IsAutoVacuumLauncherProcess() || IsGlobalStatsTrackerProcess())
u_sess->stat_cxt.pgStatDBHash = pgstat_read_statsfile(InvalidOid, false);
else
u_sess->stat_cxt.pgStatDBHash = pgstat_read_statsfile(u_sess->proc_cxt.MyDatabaseId, false);
}
void pgstat_read_analyzed()
{
FILE* fpin = NULL;
int32 format_id;
PgStat_StatDBEntry dbbuf;
PgStat_StatTabEntry tabbuf;
PgStat_StatFuncEntry funcbuf;
HASHCTL hash_ctl;
errno_t errorno = EOK;
PgStat_AnaCheckEntry* tabentry = NULL;
const char* statfile = u_sess->stat_cxt.pgstat_stat_filename;
bool need_collected = false;
bool found = false;
TimestampTz cur_ts;
TimestampTz min_ts;
int count;
Assert(!u_sess->stat_cxt.pgStatRunningInCollector);
cur_ts = GetCurrentTimestamp();
min_ts = TimestampTzPlusMilliseconds(cur_ts, -PGSTAT_STAT_INTERVAL);
for (count = 0; count < PGSTAT_POLL_LOOP_COUNT; count++) {
TimestampTz file_ts = 0;
CHECK_FOR_INTERRUPTS();
if (pgstat_read_statsfile_timestamp(false, &file_ts) && file_ts >= min_ts)
break;
if ((count % PGSTAT_INQ_LOOP_COUNT) == 0)
pgstat_send_inquiry(min_ts);
pg_usleep(PGSTAT_RETRY_DELAY * 1000L);
}
if (count >= PGSTAT_POLL_LOOP_COUNT)
ereport(LOG,
(errmsg("using stale statistics instead of current ones "
"because stats collector is not responding")));
* Similar to pgstat_read_statsfile.
* The hash table will live in u_sess->stat_cxt.pgStatLocalContext
*/
pgstat_setup_memcxt();
errorno = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl));
securec_check(errorno, "\0", "\0");
hash_ctl.keysize = sizeof(Oid);
hash_ctl.entrysize = sizeof(PgStat_AnaCheckEntry);
hash_ctl.hash = oid_hash;
hash_ctl.hcxt = u_sess->stat_cxt.pgStatLocalContext;
u_sess->stat_cxt.analyzeCheckHash =
hash_create("AnalyzeCheck hash", PGSTAT_TAB_HASH_SIZE, &hash_ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
errorno = memset_s(u_sess->stat_cxt.globalStats, sizeof(PgStat_GlobalStats), 0, sizeof(PgStat_GlobalStats));
securec_check(errorno, "\0", "\0");
u_sess->stat_cxt.globalStats->stat_reset_timestamp = GetCurrentTimestamp();
if ((fpin = AllocateFile(statfile, PG_BINARY_R)) == NULL) {
if (errno != ENOENT)
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errcode_for_file_access(), errmsg("could not open statistics file \"%s\": %m", statfile)));
elog(LOG, "[Pgstat] statfile %s is missing, using empty dbhash.", statfile);
return;
}
if (fread(&format_id, 1, sizeof(format_id), fpin) != sizeof(format_id) || format_id != PGSTAT_FILE_FORMAT_ID) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
if (fread(u_sess->stat_cxt.globalStats, 1, sizeof(PgStat_GlobalStats), fpin) != sizeof(PgStat_GlobalStats)) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
for (;;) {
switch (fgetc(fpin)) {
* Database begins. Subsequently, zero to many 'T' and 'F' entries
* will follow until a 'd' is encountered.
*/
case 'D':
if (fread(&dbbuf, 1, offsetof(PgStat_StatDBEntry, tables), fpin) !=
offsetof(PgStat_StatDBEntry, tables)) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
if (dbbuf.databaseid != u_sess->proc_cxt.MyDatabaseId)
need_collected = false;
else
need_collected = true;
break;
case 'd':
need_collected = false;
break;
case 'T':
if (fread(&tabbuf, 1, sizeof(PgStat_StatTabEntry), fpin) != sizeof(PgStat_StatTabEntry)) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
if (!need_collected || tabbuf.tablekey.statFlag != STATFLG_RELATION)
break;
tabentry = (PgStat_AnaCheckEntry*)hash_search(
u_sess->stat_cxt.analyzeCheckHash, (void*)&(tabbuf.tablekey.tableid), HASH_ENTER, &found);
if (found) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
if (tabbuf.analyze_timestamp != 0)
tabentry->is_analyzed = true;
else
tabentry->is_analyzed = false;
break;
case 'F':
if (fread(&funcbuf, 1, sizeof(PgStat_StatFuncEntry), fpin) != sizeof(PgStat_StatFuncEntry)) {
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
break;
case 'E':
goto done;
default:
ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING,
(errmsg("corrupted statistics file \"%s\"", statfile)));
goto done;
}
}
done:
(void)FreeFile(fpin);
}
* pgstat_setup_memcxt() -
*
* Create u_sess->stat_cxt.pgStatLocalContext, if not already done.
* ----------
*/
void pgstat_setup_memcxt(void)
{
if (u_sess->stat_cxt.pgStatLocalContext == NULL)
u_sess->stat_cxt.pgStatLocalContext = AllocSetContextCreate(u_sess->top_mem_cxt,
"Statistics snapshot",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
}
void pgstat_clean_memcxt(void)
{
if (u_sess->stat_cxt.pgStatLocalContext != NULL) {
MemoryContextDelete(u_sess->stat_cxt.pgStatLocalContext);
u_sess->stat_cxt.pgStatLocalContext = NULL;
}
}
static void pgstat_collect_thread_status_setup_memcxt(void)
{
Assert(u_sess->stat_cxt.pgStatLocalContext);
Assert(!u_sess->stat_cxt.pgStatCollectThdStatusContext);
u_sess->stat_cxt.pgStatCollectThdStatusContext = AllocSetContextCreate(u_sess->stat_cxt.pgStatLocalContext,
"PgStatCollectThdStatus",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
}
static void pgstat_collect_thread_status_clear_resource(void)
{
MemoryContextDelete(u_sess->stat_cxt.pgStatCollectThdStatusContext);
u_sess->stat_cxt.pgStatCollectThdStatusContext = NULL;
u_sess->stat_cxt.localBackendStatusTable = NULL;
u_sess->stat_cxt.localNumBackends = 0;
}
* pgstat_clear_snapshot() -
*
* Discard any data collected in the current transaction. Any subsequent
* request will cause new snapshots to be read.
*
* This is also invoked during transaction commit or abort to discard
* the no-longer-wanted snapshot.
* ----------
*/
void pgstat_clear_snapshot(void)
{
if (u_sess->stat_cxt.pgStatLocalContext)
MemoryContextDelete(u_sess->stat_cxt.pgStatLocalContext);
u_sess->stat_cxt.pgStatLocalContext = NULL;
u_sess->stat_cxt.pgStatDBHash = NULL;
u_sess->stat_cxt.localBackendStatusTable = NULL;
u_sess->stat_cxt.localNumBackends = 0;
u_sess->stat_cxt.analyzeCheckHash = NULL;
}
* pgstat_recv_inquiry() -
*
* Process stat inquiry requests.
* ----------
*/
static void pgstat_recv_inquiry(PgStat_MsgInquiry* msg, int len)
{
if (msg->inquiry_time > g_instance.stat_cxt.last_statrequest)
g_instance.stat_cxt.last_statrequest = msg->inquiry_time;
}
static inline void init_tabentry_truncate(PgStat_StatTabEntry* tabentry, PgStat_TableEntry* tabmsg)
{
tabentry->n_live_tuples = 0;
tabentry->n_dead_tuples = 0;
}
static void overflow_check(int64 stat_value, int64 add_value)
{
if (stat_value > LONG_LONG_MAX - add_value) {
ereport(ERROR, (errmsg("Integer overflow when update database-wid stats")));
}
}
* pgstat_recv_tabstat() -
*
* Count what the backend has done.
* ----------
*/
static void pgstat_recv_tabstat(PgStat_MsgTabstat* msg, int len)
{
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatTabEntry* tabentry = NULL;
int i;
bool found = false;
dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
if (dbentry == NULL) {
ereport(ERROR, (errmsg("Failed to get database-wide stats.")));
}
* Check and Update database-wide stats.
*/
overflow_check(dbentry->n_xact_commit, (PgStat_Counter)(msg->m_xact_commit));
overflow_check(dbentry->n_xact_rollback, (PgStat_Counter)(msg->m_xact_rollback));
overflow_check(dbentry->n_block_read_time, msg->m_block_read_time);
overflow_check(dbentry->n_block_write_time, msg->m_block_write_time);
dbentry->n_xact_commit += (PgStat_Counter)(msg->m_xact_commit);
dbentry->n_xact_rollback += (PgStat_Counter)(msg->m_xact_rollback);
dbentry->n_block_read_time += msg->m_block_read_time;
dbentry->n_block_write_time += msg->m_block_write_time;
* Process all table entries in the message.
*/
for (i = 0; i < msg->m_nentries; i++) {
PgStat_TableEntry* tabmsg = &(msg->m_entry[i]);
PgStat_StatTabKey tabkey;
tabkey.statFlag = tabmsg->t_statFlag;
tabkey.tableid = tabmsg->t_id;
tabentry = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)(&tabkey), HASH_ENTER, &found);
if (!found) {
* If it's a new table entry, initialize counters to the values we
* just got.
*/
tabentry->numscans = tabmsg->t_counts.t_numscans;
tabentry->tuples_returned = tabmsg->t_counts.t_tuples_returned;
tabentry->tuples_fetched = tabmsg->t_counts.t_tuples_fetched;
tabentry->tuples_inserted = tabmsg->t_counts.t_tuples_inserted;
tabentry->tuples_updated = tabmsg->t_counts.t_tuples_updated;
tabentry->tuples_deleted = tabmsg->t_counts.t_tuples_deleted;
tabentry->tuples_inplace_updated = tabmsg->t_counts.t_tuples_inplace_updated;
tabentry->tuples_hot_updated = tabmsg->t_counts.t_tuples_hot_updated;
if (tabmsg->t_counts.t_truncated) {
tabentry->n_live_tuples = 0;
tabentry->n_dead_tuples = 0;
}
tabentry->n_live_tuples = tabmsg->t_counts.t_delta_live_tuples;
tabentry->n_dead_tuples = tabmsg->t_counts.t_delta_dead_tuples;
tabentry->changes_since_analyze = tabmsg->t_counts.t_changed_tuples;
tabentry->blocks_fetched = tabmsg->t_counts.t_blocks_fetched;
tabentry->blocks_hit = tabmsg->t_counts.t_blocks_hit;
tabentry->cu_mem_hit = tabmsg->t_counts.t_cu_mem_hit;
tabentry->cu_hdd_sync = tabmsg->t_counts.t_cu_hdd_sync;
tabentry->cu_hdd_asyn = tabmsg->t_counts.t_cu_hdd_asyn;
tabentry->vacuum_timestamp = 0;
tabentry->vacuum_count = 0;
tabentry->autovac_vacuum_timestamp = 0;
tabentry->autovac_vacuum_count = 0;
tabentry->analyze_timestamp = 0;
tabentry->analyze_count = 0;
tabentry->autovac_analyze_timestamp = 0;
tabentry->autovac_analyze_count = 0;
tabentry->autovac_status = 0;
tabentry->data_changed_timestamp = 0;
tabentry->success_prune_cnt = 0;
tabentry->total_prune_cnt = 0;
} else {
* Otherwise add the values to the existing entry.
*/
tabentry->numscans += tabmsg->t_counts.t_numscans;
tabentry->tuples_returned += tabmsg->t_counts.t_tuples_returned;
tabentry->tuples_fetched += tabmsg->t_counts.t_tuples_fetched;
tabentry->tuples_inserted += tabmsg->t_counts.t_tuples_inserted;
tabentry->tuples_updated += tabmsg->t_counts.t_tuples_updated;
tabentry->tuples_deleted += tabmsg->t_counts.t_tuples_deleted;
tabentry->tuples_inplace_updated += tabmsg->t_counts.t_tuples_inplace_updated;
tabentry->tuples_hot_updated += tabmsg->t_counts.t_tuples_hot_updated;
if (tabmsg->t_counts.t_truncated) {
tabentry->n_live_tuples = 0;
tabentry->n_dead_tuples = 0;
}
tabentry->n_live_tuples += tabmsg->t_counts.t_delta_live_tuples;
tabentry->n_dead_tuples += tabmsg->t_counts.t_delta_dead_tuples;
tabentry->changes_since_analyze += tabmsg->t_counts.t_changed_tuples;
tabentry->blocks_fetched += tabmsg->t_counts.t_blocks_fetched;
tabentry->blocks_hit += tabmsg->t_counts.t_blocks_hit;
tabentry->cu_mem_hit += tabmsg->t_counts.t_cu_mem_hit;
tabentry->cu_hdd_sync += tabmsg->t_counts.t_cu_hdd_sync;
tabentry->cu_hdd_asyn += tabmsg->t_counts.t_cu_hdd_asyn;
}
tabentry->n_live_tuples = Max(tabentry->n_live_tuples, 0);
tabentry->n_dead_tuples = Max(tabentry->n_dead_tuples, 0);
* Add per-table stats to the per-database entry, too.
*/
dbentry->n_tuples_returned += tabmsg->t_counts.t_tuples_returned;
dbentry->n_tuples_fetched += tabmsg->t_counts.t_tuples_fetched;
dbentry->n_tuples_inserted += tabmsg->t_counts.t_tuples_inserted;
dbentry->n_tuples_updated += tabmsg->t_counts.t_tuples_updated;
dbentry->n_tuples_deleted += tabmsg->t_counts.t_tuples_deleted;
dbentry->n_blocks_fetched += tabmsg->t_counts.t_blocks_fetched;
dbentry->n_blocks_hit += tabmsg->t_counts.t_blocks_hit;
dbentry->n_cu_mem_hit += tabmsg->t_counts.t_cu_mem_hit;
dbentry->n_cu_hdd_sync += tabmsg->t_counts.t_cu_hdd_sync;
dbentry->n_cu_hdd_asyn += tabmsg->t_counts.t_cu_hdd_asyn;
if (pg_stat_relation(tabkey.statFlag))
continue;
tabkey.tableid = tabmsg->t_statFlag;
tabkey.statFlag = InvalidOid;
tabentry = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)(&tabkey), HASH_ENTER, &found);
if (!found) {
* If it's a new table entry, initialize counters to the values we
* just got.
*/
tabentry->numscans = tabmsg->t_counts.t_numscans;
tabentry->tuples_returned = tabmsg->t_counts.t_tuples_returned;
tabentry->tuples_fetched = tabmsg->t_counts.t_tuples_fetched;
tabentry->tuples_inserted = tabmsg->t_counts.t_tuples_inserted;
tabentry->tuples_updated = tabmsg->t_counts.t_tuples_updated;
tabentry->tuples_deleted = tabmsg->t_counts.t_tuples_deleted;
tabentry->tuples_hot_updated = tabmsg->t_counts.t_tuples_hot_updated;
tabentry->tuples_inplace_updated = tabmsg->t_counts.t_tuples_inplace_updated;
tabentry->n_live_tuples = tabmsg->t_counts.t_delta_live_tuples;
tabentry->n_dead_tuples = tabmsg->t_counts.t_delta_dead_tuples;
tabentry->changes_since_analyze = tabmsg->t_counts.t_changed_tuples;
tabentry->blocks_fetched = tabmsg->t_counts.t_blocks_fetched;
tabentry->blocks_hit = tabmsg->t_counts.t_blocks_hit;
tabentry->cu_mem_hit = tabmsg->t_counts.t_cu_mem_hit;
tabentry->cu_hdd_sync = tabmsg->t_counts.t_cu_hdd_sync;
tabentry->cu_hdd_asyn = tabmsg->t_counts.t_cu_hdd_asyn;
tabentry->vacuum_timestamp = 0;
tabentry->vacuum_count = 0;
tabentry->autovac_vacuum_timestamp = 0;
tabentry->autovac_vacuum_count = 0;
tabentry->analyze_timestamp = 0;
tabentry->analyze_count = 0;
tabentry->autovac_analyze_timestamp = 0;
tabentry->autovac_analyze_count = 0;
tabentry->autovac_status = 0;
tabentry->data_changed_timestamp = 0;
tabentry->success_prune_cnt = 0;
tabentry->total_prune_cnt = 0;
} else {
* Otherwise add the values to the existing entry.
*/
tabentry->numscans += tabmsg->t_counts.t_numscans;
tabentry->tuples_returned += tabmsg->t_counts.t_tuples_returned;
tabentry->tuples_fetched += tabmsg->t_counts.t_tuples_fetched;
tabentry->tuples_inserted += tabmsg->t_counts.t_tuples_inserted;
tabentry->tuples_updated += tabmsg->t_counts.t_tuples_updated;
tabentry->tuples_deleted += tabmsg->t_counts.t_tuples_deleted;
tabentry->tuples_hot_updated += tabmsg->t_counts.t_tuples_hot_updated;
tabentry->tuples_inplace_updated += tabmsg->t_counts.t_tuples_inplace_updated;
tabentry->n_live_tuples += tabmsg->t_counts.t_delta_live_tuples;
tabentry->n_dead_tuples += tabmsg->t_counts.t_delta_dead_tuples;
tabentry->changes_since_analyze += tabmsg->t_counts.t_changed_tuples;
tabentry->blocks_fetched += tabmsg->t_counts.t_blocks_fetched;
tabentry->blocks_hit += tabmsg->t_counts.t_blocks_hit;
tabentry->cu_mem_hit += tabmsg->t_counts.t_cu_mem_hit;
tabentry->cu_hdd_sync += tabmsg->t_counts.t_cu_hdd_sync;
tabentry->cu_hdd_asyn += tabmsg->t_counts.t_cu_hdd_asyn;
}
}
}
* pgstat_recv_tabpurge() -
*
* Arrange for dead table removal.
* ----------
*/
static void pgstat_recv_tabpurge(PgStat_MsgTabpurge* msg, int len)
{
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatTabKey tabkey;
PgStat_StatTabEntry* tabentry = NULL;
PgStat_StatTabKey parentkey;
PgStat_StatTabEntry* parententry = NULL;
int i;
dbentry = pgstat_get_db_entry(msg->m_databaseid, false);
* No need to purge if we don't even know the database.
*/
if ((dbentry == NULL) || (dbentry->tables == NULL))
return;
* Process all table entries in the message.
*/
for (i = 0; i < msg->m_nentries; i++) {
tabkey.statFlag = msg->m_entry[i].m_statFlag;
tabkey.tableid = msg->m_entry[i].m_tableid;
tabentry = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)(&tabkey), HASH_FIND, NULL);
if ((tabentry != NULL) && tabkey.statFlag) {
parentkey.tableid = tabkey.statFlag;
parentkey.statFlag = InvalidOid;
parententry = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)(&parentkey), HASH_FIND, NULL);
if (parententry != NULL) {
parententry->n_dead_tuples = Max(0, parententry->n_dead_tuples - tabentry->n_dead_tuples);
parententry->n_live_tuples = Max(0, parententry->n_live_tuples - tabentry->n_live_tuples);
parententry->changes_since_analyze += tabentry->changes_since_analyze;
}
}
(void*)hash_search(dbentry->tables, (void*)(&tabkey), HASH_REMOVE, NULL);
}
}
* pgstat_recv_dropdb() -
*
* Arrange for dead database removal
* ----------
*/
static void pgstat_recv_dropdb(PgStat_MsgDropdb* msg, int len)
{
PgStat_StatDBEntry* dbentry = NULL;
Oid db_oid = msg->m_databaseid;
* Lookup the database in the hashtable.
*/
dbentry = pgstat_get_db_entry(db_oid, false);
* If found, remove it.
*/
if (NULL != dbentry) {
if (dbentry->tables != NULL)
hash_destroy(dbentry->tables);
if (dbentry->functions != NULL)
hash_destroy(dbentry->functions);
if (hash_search(u_sess->stat_cxt.pgStatDBHash, (void*)&(db_oid), HASH_REMOVE, NULL) == NULL) {
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg("database hash table corrupted "
"during cleanup --- abort")));
}
}
* delete database related hotkeys statistics
*/
g_instance.stat_cxt.lru->DeleteHotkeysInDB(db_oid);
}
* pgstat_recv_resetcounter() -
*
* Reset the statistics for the specified database.
* ----------
*/
static void pgstat_recv_resetcounter(PgStat_MsgResetcounter* msg, int len)
{
HASHCTL hash_ctl;
PgStat_StatDBEntry* dbentry = NULL;
errno_t rc = EOK;
* Lookup the database in the hashtable. Nothing to do if not there.
*/
dbentry = pgstat_get_db_entry(msg->m_databaseid, false);
if (dbentry == NULL)
return;
* We simply throw away all the database's table entries by recreating a
* new hash table for them.
*/
if (dbentry->tables != NULL)
hash_destroy(dbentry->tables);
if (dbentry->functions != NULL)
hash_destroy(dbentry->functions);
dbentry->tables = NULL;
dbentry->functions = NULL;
(void)gs_lock_test_and_set_64(&g_instance.stat_cxt.NodeStatResetTime, GetCurrentTimestamp());
* Reset database-level stats too. This should match the initialization
* code in pgstat_get_db_entry().
*/
dbentry->n_xact_commit = 0;
dbentry->n_xact_rollback = 0;
dbentry->n_blocks_fetched = 0;
dbentry->n_blocks_hit = 0;
dbentry->n_cu_mem_hit = 0;
dbentry->n_cu_hdd_sync = 0;
dbentry->n_cu_hdd_asyn = 0;
dbentry->n_tuples_returned = 0;
dbentry->n_tuples_fetched = 0;
dbentry->n_tuples_inserted = 0;
dbentry->n_tuples_updated = 0;
dbentry->n_tuples_deleted = 0;
dbentry->last_autovac_time = 0;
dbentry->n_temp_bytes = 0;
dbentry->n_temp_files = 0;
dbentry->n_deadlocks = 0;
dbentry->n_block_read_time = 0;
dbentry->n_block_write_time = 0;
dbentry->n_mem_mbytes_reserved = 0;
dbentry->stat_reset_timestamp = GetCurrentTimestamp();
rc = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl));
securec_check(rc, "\0", "\0");
hash_ctl.keysize = sizeof(PgStat_StatTabKey);
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);
hash_ctl.keysize = sizeof(Oid);
hash_ctl.entrysize = sizeof(PgStat_StatFuncEntry);
hash_ctl.hash = oid_hash;
dbentry->functions = hash_create("Per-database function", 512, &hash_ctl, HASH_ELEM | HASH_FUNCTION);
}
* pgstat_recv_resetshared() -
*
* Reset some shared statistics of the cluster.
* ----------
*/
static void pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter* msg, int len)
{
errno_t rc = EOK;
if (msg->m_resettarget == RESET_BGWRITER) {
rc = memset_s(u_sess->stat_cxt.globalStats, sizeof(PgStat_GlobalStats), 0, sizeof(PgStat_GlobalStats));
securec_check(rc, "\0", "\0");
u_sess->stat_cxt.globalStats->stat_reset_timestamp = GetCurrentTimestamp();
gs_lock_test_and_set_64(&g_instance.stat_cxt.NodeStatResetTime, GetCurrentTimestamp());
}
* Presumably the sender of this message validated the target, don't
* complain here if it's not valid
*/
}
* pgstat_recv_resetsinglecounter() -
*
* Reset a statistics for a single object
* ----------
*/
static void pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter* msg, int len)
{
PgStat_StatDBEntry* dbentry = NULL;
dbentry = pgstat_get_db_entry(msg->m_databaseid, false);
if (dbentry == NULL)
return;
dbentry->stat_reset_timestamp = GetCurrentTimestamp();
gs_lock_test_and_set_64(&g_instance.stat_cxt.NodeStatResetTime, GetCurrentTimestamp());
if (msg->m_resettype == RESET_TABLE) {
PgStat_StatTabKey tabkey;
tabkey.statFlag = msg->p_objectid;
tabkey.tableid = msg->m_objectid;
(void)hash_search(dbentry->tables, (void*)&(tabkey), HASH_REMOVE, NULL);
} else if (msg->m_resettype == RESET_FUNCTION) {
(void)hash_search(dbentry->functions, (void*)&(msg->m_objectid), HASH_REMOVE, NULL);
}
}
* pgstat_recv_autovac() -
*
* Process an autovacuum signalling message.
* ----------
*/
static void pgstat_recv_autovac(PgStat_MsgAutovacStart* msg, int len)
{
PgStat_StatDBEntry* dbentry = NULL;
* Store the last autovacuum time in the database's hashtable entry.
*/
dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
dbentry->last_autovac_time = msg->m_start_time;
}
* pgstat_recv_vacuum() -
*
* Process a VACUUM message.
* ----------
*/
static void pgstat_recv_vacuum(PgStat_MsgVacuum* msg, int len)
{
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatTabEntry* tabentry = NULL;
* Store the data in the table's hashtable entry.
*/
dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
tabentry = pgstat_get_tab_entry(dbentry, msg->m_tableoid, true, msg->m_statFlag);
if (msg->m_tuples < 0) {
tabentry->n_dead_tuples = 0;
} else {
tabentry->n_dead_tuples = Max(0, tabentry->n_dead_tuples - msg->m_tuples);
tabentry->changes_since_analyze += msg->m_tuples;
}
if (msg->m_autovacuum) {
tabentry->autovac_vacuum_timestamp = msg->m_vacuumtime;
tabentry->autovac_vacuum_count++;
tabentry->vacuum_timestamp = msg->m_vacuumtime;
tabentry->vacuum_count++;
} else {
tabentry->vacuum_timestamp = msg->m_vacuumtime;
tabentry->vacuum_count++;
}
if (OidIsValid(msg->m_statFlag)) {
PgStat_StatTabEntry* main_tabentry = pgstat_get_tab_entry(dbentry, msg->m_statFlag, false, InvalidOid);
if (main_tabentry != NULL) {
if (msg->m_tuples < 0)
main_tabentry->n_dead_tuples = 0;
else
main_tabentry->n_dead_tuples = Max(0, main_tabentry->n_dead_tuples - msg->m_tuples);
}
}
}
* PgstatRecvPrunestat() -
*
* Process a pruning message
* ----------
*/
static void PgstatRecvPrunestat(PgStat_MsgPrune* msg, int len)
{
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatTabEntry* tabentry = NULL;
* Store the data in the table's hashtable entry.
*/
dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
tabentry = pgstat_get_tab_entry(dbentry, msg->m_tableoid, true, msg->m_statFlag);
tabentry->success_prune_cnt += msg->m_pruned_blocks;
tabentry->total_prune_cnt += msg->m_scanned_blocks;
}
* pgstat_recv_data_changed() -
*
* Process a insert/delete/update/copy/[exchange/truncate/drop] partition message.
* ----------
*/
static void pgstat_recv_data_changed(PgStat_MsgDataChanged* msg, int len)
{
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatTabEntry* tabentry = NULL;
* Store the data in the table's hashtable entry.
*/
dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
tabentry = pgstat_get_tab_entry(dbentry, msg->m_tableoid, true, msg->m_statFlag);
tabentry->data_changed_timestamp = msg->m_changed_time;
}
* pgstat_recv_autovac_stat() -
*
* Process a autovac stat message.
* ----------
*/
static void pgstat_recv_autovac_stat(PgStat_MsgAutovacStat* msg, int len)
{
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatTabEntry* tabentry = NULL;
* Store the data in the table's hashtable entry.
*/
dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
tabentry = pgstat_get_tab_entry(dbentry, msg->m_tableoid, true, msg->m_statFlag);
if (tabentry && AV_TIMEOUT == msg->m_autovacStat) {
increase_continued_timeout(tabentry->autovac_status);
increase_toatl_timeout(tabentry->autovac_status);
}
}
* pgstat_recv_truncate() -
*
* Process a TRUNCATE message.
* ----------
*/
static void pgstat_recv_truncate(PgStat_MsgTruncate* msg, int len)
{
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatTabEntry* tabentry = NULL;
PgStat_StatTabEntry* parent_entry = NULL;
* Store the data in the table's hashtable entry.
*/
dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
tabentry = pgstat_get_tab_entry(dbentry, msg->m_tableoid, true, msg->m_statFlag);
if (msg->m_statFlag) {
parent_entry = pgstat_get_tab_entry(dbentry, msg->m_statFlag, true, InvalidOid);
if (NULL != parent_entry) {
parent_entry->n_dead_tuples = Max(0, parent_entry->n_dead_tuples - tabentry->n_dead_tuples);
parent_entry->n_live_tuples = Max(0, parent_entry->n_live_tuples - tabentry->n_live_tuples);
parent_entry->changes_since_analyze += tabentry->changes_since_analyze;
}
}
tabentry->n_dead_tuples = 0;
tabentry->n_live_tuples = 0;
}
* pgstat_recv_analyze() -
*
* Process an ANALYZE message.
* ----------
*/
static void pgstat_recv_analyze(PgStat_MsgAnalyze* msg, int len)
{
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatTabEntry* tabentry = NULL;
* Store the data in the table's hashtable entry.
*/
dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
tabentry = pgstat_get_tab_entry(dbentry, msg->m_tableoid, true, msg->m_statFlag);
tabentry->n_live_tuples = msg->m_live_tuples;
tabentry->n_dead_tuples = msg->m_dead_tuples;
* We reset changes_since_analyze to zero, forgetting any changes that
* occurred while the ANALYZE was in progress.
*/
tabentry->changes_since_analyze = 0;
if (msg->m_autovacuum) {
tabentry->autovac_analyze_timestamp = msg->m_analyzetime;
tabentry->autovac_analyze_count++;
tabentry->analyze_timestamp = msg->m_analyzetime;
tabentry->analyze_count++;
reset_continued_timeout(tabentry->autovac_status);
} else {
tabentry->analyze_timestamp = msg->m_analyzetime;
tabentry->analyze_count++;
}
}
* pgstat_recv_bgwriter() -
*
* Process a BGWRITER message.
* ----------
*/
static void pgstat_recv_bgwriter(PgStat_MsgBgWriter* msg, int len)
{
if (u_sess->stat_cxt.globalStats->timed_checkpoints > (INT64_MAX - msg->m_timed_checkpoints)) {
ereport(ERROR, (errmsg("timed_checkpoints overflow")));
}
u_sess->stat_cxt.globalStats->timed_checkpoints += msg->m_timed_checkpoints;
if (u_sess->stat_cxt.globalStats->requested_checkpoints > (INT64_MAX - msg->m_requested_checkpoints)) {
ereport(ERROR, (errmsg("requested_checkpoints overflow")));
}
u_sess->stat_cxt.globalStats->requested_checkpoints += msg->m_requested_checkpoints;
if (u_sess->stat_cxt.globalStats->checkpoint_write_time > (INT64_MAX - msg->m_checkpoint_write_time)) {
ereport(ERROR, (errmsg("checkpoint_write_time overflow")));
}
u_sess->stat_cxt.globalStats->checkpoint_write_time += msg->m_checkpoint_write_time;
if (u_sess->stat_cxt.globalStats->checkpoint_sync_time > (INT64_MAX - msg->m_checkpoint_sync_time)) {
ereport(ERROR, (errmsg("checkpoint_sync_time overflow")));
}
u_sess->stat_cxt.globalStats->checkpoint_sync_time += msg->m_checkpoint_sync_time;
if (u_sess->stat_cxt.globalStats->buf_written_checkpoints > (INT64_MAX - msg->m_buf_written_checkpoints)) {
ereport(ERROR, (errmsg("buf_written_checkpoints overflow")));
}
u_sess->stat_cxt.globalStats->buf_written_checkpoints += msg->m_buf_written_checkpoints;
if (u_sess->stat_cxt.globalStats->buf_written_clean > (INT64_MAX - msg->m_buf_written_clean)) {
ereport(ERROR, (errmsg("buf_written_clean overflow")));
}
u_sess->stat_cxt.globalStats->buf_written_clean += msg->m_buf_written_clean;
if (u_sess->stat_cxt.globalStats->maxwritten_clean > (INT64_MAX - msg->m_maxwritten_clean)) {
ereport(ERROR, (errmsg("maxwritten_clean overflow")));
}
u_sess->stat_cxt.globalStats->maxwritten_clean += msg->m_maxwritten_clean;
if (u_sess->stat_cxt.globalStats->buf_written_backend > (INT64_MAX - msg->m_buf_written_backend)) {
ereport(ERROR, (errmsg("buf_written_backend overflow")));
}
u_sess->stat_cxt.globalStats->buf_written_backend += msg->m_buf_written_backend;
if (u_sess->stat_cxt.globalStats->buf_fsync_backend > (INT64_MAX - msg->m_buf_fsync_backend)) {
ereport(ERROR, (errmsg("buf_fsync_backend overflow")));
}
u_sess->stat_cxt.globalStats->buf_fsync_backend += msg->m_buf_fsync_backend;
if (u_sess->stat_cxt.globalStats->buf_alloc > (INT64_MAX - msg->m_buf_alloc)) {
ereport(ERROR, (errmsg("buf_alloc overflow")));
}
u_sess->stat_cxt.globalStats->buf_alloc += msg->m_buf_alloc;
}
* pgstat_recv_recoveryconflict() -
*
* Process a RECOVERYCONFLICT message.
* ----------
*/
static void pgstat_recv_recoveryconflict(const PgStat_MsgRecoveryConflict* msg)
{
PgStat_StatDBEntry* dbentry = NULL;
dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
switch (msg->m_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->n_conflict_tablespace++;
break;
case PROCSIG_RECOVERY_CONFLICT_LOCK:
dbentry->n_conflict_lock++;
break;
case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
dbentry->n_conflict_snapshot++;
break;
case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
dbentry->n_conflict_bufferpin++;
break;
case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
dbentry->n_conflict_startup_deadlock++;
break;
default:
ereport(ERROR,
(errcode(ERRCODE_CASE_NOT_FOUND),
errmsg("unrecognized bypass recovery conflict reason: %d", msg->m_reason)));
}
}
* pgstat_recv_deadlock() -
*
* Process a DEADLOCK message.
* ----------
*/
static void pgstat_recv_deadlock(const PgStat_MsgDeadlock* msg)
{
PgStat_StatDBEntry* dbentry = NULL;
dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
dbentry->n_deadlocks++;
}
* pgstat_recv_tempfile() -
*
* Process a TEMPFILE message.
* ----------
*/
static void pgstat_recv_tempfile(PgStat_MsgTempFile* msg, int len)
{
PgStat_StatDBEntry* dbentry = NULL;
dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
dbentry->n_temp_bytes += msg->m_filesize;
dbentry->n_temp_files += 1;
}
void pgstate_update_percentile_responsetime(void)
{
if (IS_PGXC_COORDINATOR || IS_SINGLE_NODE) {
int64 elapse_start = GetCurrentStatementLocalStartTimestamp();
if (elapse_start != 0) {
int64 duration = GetCurrentTimestamp() - elapse_start;
if (IS_SINGLE_NODE) {
pgstat_update_responstime_singlenode(u_sess->unique_sql_cxt.unique_sql_id, elapse_start, duration);
} else {
pgstat_report_sql_rt(u_sess->unique_sql_cxt.unique_sql_id, elapse_start, duration);
}
}
}
}
void pgstat_update_responstime_singlenode(uint64 UniqueSQLId, int64 start_time, int64 rt)
{
int32 sqlRTIndex;
if (!u_sess->attr.attr_common.enable_instr_rt_percentile)
return;
if (g_instance.stat_cxt.force_process != false || g_instance.stat_cxt.sql_rt_info_array == NULL) {
return;
}
LWLockAcquire(PercentileLock, LW_SHARED);
sqlRTIndex = gs_atomic_add_32(&g_instance.stat_cxt.sql_rt_info_array->sqlRTIndex, 1);
if ((sqlRTIndex >= MAX_SQL_RT_INFO_COUNT) || (sqlRTIndex < 0)) {
g_instance.stat_cxt.force_process = true;
LWLockRelease(PercentileLock);
return;
}
g_instance.stat_cxt.sql_rt_info_array->sqlRT[sqlRTIndex].UniqueSQLId = UniqueSQLId;
g_instance.stat_cxt.sql_rt_info_array->sqlRT[sqlRTIndex].start_time = start_time;
g_instance.stat_cxt.sql_rt_info_array->sqlRT[sqlRTIndex].rt = rt;
LWLockRelease(PercentileLock);
}
static void pgstat_recv_sql_responstime(PgStat_SqlRT* msg, int len)
{
if (g_instance.stat_cxt.sql_rt_info_array == NULL) {
return;
}
LWLockAcquire(PercentileLock, LW_EXCLUSIVE);
int32 sqlRTIndex = g_instance.stat_cxt.sql_rt_info_array->sqlRTIndex;
if (sqlRTIndex == MAX_SQL_RT_INFO_COUNT) {
g_instance.stat_cxt.sql_rt_info_array->isFull = true;
if (PgxcIsCentralCoordinator(g_instance.attr.attr_common.PGXCNodeName)) {
g_instance.stat_cxt.force_process = true;
LWLockRelease(PercentileLock);
while (g_instance.stat_cxt.force_process) {
pg_usleep(PGSTAT_RETRY_DELAY);
}
LWLockAcquire(PercentileLock, LW_EXCLUSIVE);
}
sqlRTIndex = 0;
g_instance.stat_cxt.sql_rt_info_array->sqlRTIndex = sqlRTIndex;
}
g_instance.stat_cxt.sql_rt_info_array->sqlRT[sqlRTIndex].UniqueSQLId = msg->sqlRT.UniqueSQLId;
g_instance.stat_cxt.sql_rt_info_array->sqlRT[sqlRTIndex].start_time = msg->sqlRT.start_time;
g_instance.stat_cxt.sql_rt_info_array->sqlRT[sqlRTIndex].rt = msg->sqlRT.rt;
g_instance.stat_cxt.sql_rt_info_array->sqlRTIndex = g_instance.stat_cxt.sql_rt_info_array->sqlRTIndex + 1;
LWLockRelease(PercentileLock);
}
static int sqlRTComparator(const void* s1, const void* s2)
{
const SqlRTInfo* sqlRT1 = (SqlRTInfo*)s1;
const SqlRTInfo* sqlRT2 = (SqlRTInfo*)s2;
if (sqlRT1->rt > sqlRT2->rt)
return 1;
else if (sqlRT1->rt < sqlRT2->rt)
return -1;
else
return 0;
}
static void prepare_calculate(SqlRTInfoArray* sql_rt_info, int* counter)
{
int sql_rt_info_count = 0;
LWLockAcquire(PercentileLock, LW_SHARED);
if (sql_rt_info->isFull)
sql_rt_info_count = MAX_SQL_RT_INFO_COUNT;
else
sql_rt_info_count = sql_rt_info->sqlRTIndex;
*counter = 0;
if (sql_rt_info_count == 0) {
sql_rt_info->isFull = false;
LWLockRelease(PercentileLock);
return;
}
if (u_sess->percentile_cxt.LocalsqlRT != NULL)
pfree_ext(u_sess->percentile_cxt.LocalsqlRT);
u_sess->percentile_cxt.LocalsqlRT = (SqlRTInfo*)MemoryContextAlloc(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), sql_rt_info_count * sizeof(SqlRTInfo));
int ss_rc = memset_s(u_sess->percentile_cxt.LocalsqlRT,
sql_rt_info_count * sizeof(SqlRTInfo),
0,
sql_rt_info_count * sizeof(SqlRTInfo));
securec_check(ss_rc, "\0", "\0");
ss_rc = memcpy_s(u_sess->percentile_cxt.LocalsqlRT,
sql_rt_info_count * sizeof(SqlRTInfo),
sql_rt_info->sqlRT,
sql_rt_info_count * sizeof(SqlRTInfo));
securec_check(ss_rc, "\0", "\0");
sql_rt_info->isFull = false;
sql_rt_info->sqlRTIndex = 0;
LWLockRelease(PercentileLock);
*counter = sql_rt_info_count;
qsort(u_sess->percentile_cxt.LocalsqlRT, sql_rt_info_count, sizeof(SqlRTInfo), sqlRTComparator);
}
static void prepare_calculate_single(const SqlRTInfoArray* sql_rt_info, int* counter)
{
int32 sql_rt_info_count = g_instance.stat_cxt.sql_rt_info_array->sqlRTIndex;
if (sql_rt_info_count > MAX_SQL_RT_INFO_COUNT) {
sql_rt_info_count = MAX_SQL_RT_INFO_COUNT;
}
*counter = 0;
if (sql_rt_info_count == 0) {
return;
}
if (u_sess->percentile_cxt.LocalsqlRT != NULL)
pfree_ext(u_sess->percentile_cxt.LocalsqlRT);
u_sess->percentile_cxt.LocalsqlRT = (SqlRTInfo*)MemoryContextAllocZero(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), sql_rt_info_count * sizeof(SqlRTInfo));
LWLockAcquire(PercentileLock, LW_EXCLUSIVE);
errno_t ss_rc = memcpy_s(u_sess->percentile_cxt.LocalsqlRT,
sql_rt_info_count * sizeof(SqlRTInfo),
sql_rt_info->sqlRT,
sql_rt_info_count * sizeof(SqlRTInfo));
LWLockRelease(PercentileLock);
securec_check(ss_rc, "\0", "\0");
g_instance.stat_cxt.sql_rt_info_array->sqlRTIndex = 0;
g_instance.stat_cxt.force_process = false;
*counter = sql_rt_info_count;
qsort(u_sess->percentile_cxt.LocalsqlRT, sql_rt_info_count, sizeof(SqlRTInfo), sqlRTComparator);
}
* pgstat_recv_memReserved() -
*
* Process a reserving-memory message.
* ----------
*/
static void pgstat_recv_memReserved(const PgStat_MsgMemReserved* msg)
{
PgStat_StatDBEntry* dbentry = NULL;
dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
if (msg->m_reserve_or_release == 1)
dbentry->n_mem_mbytes_reserved += msg->m_memMbytes;
else if (msg->m_reserve_or_release == -1)
dbentry->n_mem_mbytes_reserved -= msg->m_memMbytes;
if (dbentry->n_mem_mbytes_reserved < 0)
dbentry->n_mem_mbytes_reserved = 0;
}
* pgstat_recv_funcstat() -
*
* Count what the backend has done.
* ----------
*/
static void pgstat_recv_funcstat(PgStat_MsgFuncstat* msg, int len)
{
PgStat_FunctionEntry* funcmsg = &(msg->m_entry[0]);
PgStat_StatDBEntry* dbentry = NULL;
PgStat_StatFuncEntry* funcentry = NULL;
int i;
bool found = false;
dbentry = pgstat_get_db_entry(msg->m_databaseid, true);
* Process all function entries in the message.
*/
for (i = 0; i < msg->m_nentries; i++, funcmsg++) {
funcentry = (PgStat_StatFuncEntry*)hash_search(dbentry->functions, (void*)&(funcmsg->f_id), HASH_ENTER, &found);
if (!found) {
* If it's a new function entry, initialize counters to the values
* we just got.
*/
funcentry->f_numcalls = funcmsg->f_numcalls;
funcentry->f_total_time = funcmsg->f_total_time;
funcentry->f_self_time = funcmsg->f_self_time;
} else {
* Otherwise add the values to the existing entry.
*/
funcentry->f_numcalls += funcmsg->f_numcalls;
funcentry->f_total_time += funcmsg->f_total_time;
funcentry->f_self_time += funcmsg->f_self_time;
}
}
}
* pgstat_recv_funcpurge() -
*
* Arrange for dead function removal.
* ----------
*/
static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge* msg, int len)
{
PgStat_StatDBEntry* dbentry = NULL;
int i;
dbentry = pgstat_get_db_entry(msg->m_databaseid, false);
* No need to purge if we don't even know the database.
*/
if ((dbentry == NULL) || (dbentry->functions == NULL))
return;
* Process all function entries in the message.
*/
for (i = 0; i < msg->m_nentries; i++) {
(void)hash_search(dbentry->functions, (void*)&(msg->m_functionid[i]), HASH_REMOVE, NULL);
}
}
void pgstat_initstats_partition(Partition part)
{
Oid part_id = part->pd_id;
if (PGINVALID_SOCKET == g_instance.stat_cxt.pgStatSock || !u_sess->attr.attr_common.pgstat_track_counts) {
part->pd_pgstat_info = NULL;
return;
}
Oid relid;
if (part->pd_part->parttype == PART_OBJ_TYPE_TABLE_SUB_PARTITION) {
relid = partid_get_parentid(part->pd_part->parentid);
} else {
relid = PartitionGetRelid(part);
}
part->pd_pgstat_info = get_tabstat_entry(part_id, false, relid);
}
*we calculate cpu numbers from sysfs, so we should make sure we can access this file system.
*/
static bool checkSysFileSystem(void)
{
if (access(SysFileSystemPath, F_OK))
return false;
if (access(ThreadSiblingFile, F_OK))
return false;
if (access(CoreSiblingFile, F_OK))
return false;
return true;
}
*check whether the SysCpuPath is accessable.one accessable path represented one logical cpu.
*/
static bool checkLogicalCpu(uint32 cpuNum)
{
char pathbuf[ProcPathMax] = "";
errno_t ret = sprintf_s(pathbuf, sizeof(pathbuf), SysCpuPath, cpuNum);
securec_check_ss(ret, "\0", "\0");
return access(pathbuf, F_OK) == 0;
}
#define pg_isxdigit(c) \
(((c) >= (int)'0' && (c) <= (int)'9') || ((c) >= (int)'a' && (c) <= (int)'f') || \
((c) >= (int)'A' && (c) <= (int)'F'))
static uint32 parseSiblingFile(const char* path)
{
int c;
uint32 result = 0;
char s[2];
FILE* fp = NULL;
union {
uint32 a : 4;
struct {
uint32 a1 : 1;
uint32 a2 : 1;
uint32 a3 : 1;
uint32 a4 : 1;
} b;
} d;
errno_t errorno = EOK;
errorno = memset_s(&d, sizeof(d), 0, sizeof(d));
securec_check_c(errorno, "\0", "\0");
if ((fp = fopen(path, "r")) != NULL) {
while ((c = fgetc(fp)) != EOF) {
if (pg_isxdigit(c)) {
s[0] = c;
s[1] = '\0';
d.a = strtoul(s, NULL, 16);
result += d.b.a1;
result += d.b.a2;
result += d.b.a3;
result += d.b.a4;
}
}
(void)fclose(fp);
}
return result;
}
*This function is to get the number of logical cpus, cores and physical cpus of the system.
*We get these infomation by analysing sysfs file system. If we failed to get the three fields,
*we just ignore them when we report. And if we got this field, we will not analyse the files
*when we call this function next time.
*
*Note: This function must be called before getCpuTimes because we need logical cpu number
*to calculate the avg cpu consumption.
*/
void getCpuNums(void)
{
uint32 cpuNum = 0;
uint32 threadPerCore = 0;
uint32 threadPerSocket = 0;
if (u_sess->stat_cxt.osStatDescArray[NUM_CPUS].got && u_sess->stat_cxt.osStatDescArray[NUM_CPU_CORES].got &&
u_sess->stat_cxt.osStatDescArray[NUM_CPU_SOCKETS].got)
return;
if (checkSysFileSystem()) {
while (checkLogicalCpu(cpuNum))
cpuNum++;
if (cpuNum > 0) {
u_sess->stat_cxt.osStatDataArray[NUM_CPUS].int32Value = cpuNum;
u_sess->stat_cxt.osStatDescArray[NUM_CPUS].got = true;
*parse the mapping files ThreadSiblingFile and CoreSiblingFile. if we failed open the file or read wrong
*data, we just ignore this field.
*/
threadPerCore = parseSiblingFile(ThreadSiblingFile);
if (threadPerCore > 0) {
u_sess->stat_cxt.osStatDataArray[NUM_CPU_CORES].int32Value = cpuNum / threadPerCore;
u_sess->stat_cxt.osStatDescArray[NUM_CPU_CORES].got = true;
}
threadPerSocket = parseSiblingFile(CoreSiblingFile);
if (threadPerSocket > 0) {
u_sess->stat_cxt.osStatDataArray[NUM_CPU_SOCKETS].int32Value = cpuNum / threadPerSocket;
u_sess->stat_cxt.osStatDescArray[NUM_CPU_SOCKETS].got = true;
}
}
}
}
*This function is to get the system cpu time consumption details. We read /proc/stat
*file for this infomation. If we failed to get the ten fields, we just ignore them when we
*report.
*
*Note: Remember to call getCpuNums before this function.
*/
void getCpuTimes(void)
{
const char* statPath = "/proc/stat";
FILE* fd = NULL;
char* line = NULL;
size_t len = 0;
uint64 readTime[NumOfCpuTimeReads];
char* temp = NULL;
int i;
errno_t rc = memset_s(readTime, sizeof(readTime), 0, sizeof(readTime));
securec_check(rc, "\0", "\0");
for (i = IDLE_TIME; i <= AVG_NICE_TIME; i++) {
u_sess->stat_cxt.osStatDescArray[i].got = false;
}
if ((fd = fopen(statPath, "r")) != NULL) {
if (gs_getline(&line, &len, fd) > 0) {
temp = line + sizeof("cpu");
for (i = 0; i < NumOfCpuTimeReads; i++) {
readTime[i] = strtoul(temp, &temp, 10);
}
u_sess->stat_cxt.osStatDataArray[USER_TIME].int64Value = JiffiesGetCentiSec(readTime[0]);
u_sess->stat_cxt.osStatDataArray[NICE_TIME].int64Value = JiffiesGetCentiSec(readTime[1]);
u_sess->stat_cxt.osStatDataArray[SYS_TIME].int64Value = JiffiesGetCentiSec(readTime[2]);
u_sess->stat_cxt.osStatDataArray[IDLE_TIME].int64Value = JiffiesGetCentiSec(readTime[3]);
u_sess->stat_cxt.osStatDataArray[IOWAIT_TIME].int64Value = JiffiesGetCentiSec(readTime[4]);
u_sess->stat_cxt.osStatDataArray[BUSY_TIME].int64Value = JiffiesGetCentiSec(readTime[0] + readTime[2]);
for (i = IDLE_TIME; i <= NICE_TIME; i++) {
u_sess->stat_cxt.osStatDescArray[i].got = true;
}
if (u_sess->stat_cxt.osStatDescArray[NUM_CPUS].got) {
u_sess->stat_cxt.osStatDataArray[AVG_USER_TIME].int64Value =
u_sess->stat_cxt.osStatDataArray[USER_TIME].int64Value /
u_sess->stat_cxt.osStatDataArray[NUM_CPUS].int32Value;
u_sess->stat_cxt.osStatDataArray[AVG_NICE_TIME].int64Value =
u_sess->stat_cxt.osStatDataArray[NICE_TIME].int64Value /
u_sess->stat_cxt.osStatDataArray[NUM_CPUS].int32Value;
u_sess->stat_cxt.osStatDataArray[AVG_SYS_TIME].int64Value =
u_sess->stat_cxt.osStatDataArray[SYS_TIME].int64Value /
u_sess->stat_cxt.osStatDataArray[NUM_CPUS].int32Value;
u_sess->stat_cxt.osStatDataArray[AVG_IDLE_TIME].int64Value =
u_sess->stat_cxt.osStatDataArray[IDLE_TIME].int64Value /
u_sess->stat_cxt.osStatDataArray[NUM_CPUS].int32Value;
u_sess->stat_cxt.osStatDataArray[AVG_IOWAIT_TIME].int64Value =
u_sess->stat_cxt.osStatDataArray[IOWAIT_TIME].int64Value /
u_sess->stat_cxt.osStatDataArray[NUM_CPUS].int32Value;
u_sess->stat_cxt.osStatDataArray[AVG_BUSY_TIME].int64Value =
u_sess->stat_cxt.osStatDataArray[BUSY_TIME].int64Value /
u_sess->stat_cxt.osStatDataArray[NUM_CPUS].int32Value;
for (i = AVG_IDLE_TIME; i <= AVG_NICE_TIME; i++) {
u_sess->stat_cxt.osStatDescArray[i].got = true;
}
}
}
if (NULL != line)
pfree(line);
fclose(fd);
}
}
*This function is to get the system virtual memory paging infomation (actually it will
*get how many bytes paged in/out due to virtual memory paging). We read /proc/vmstat
*file for this infomation. If we failed to get the two fields, we just ignore them when
*we report.
*/
void getVmStat(void)
{
const char* vmStatPath = "/proc/vmstat";
int fd = -1;
char buffer[VmStatFileReadBuffer + 1];
char* temp = NULL;
uint64 inPages = 0;
uint64 outPages = 0;
uint64 pageSize = sysconf(_SC_PAGE_SIZE);
if (pageSize == 0) {
ereport(ERROR, (errmsg("Invalid page size")));
}
u_sess->stat_cxt.osStatDescArray[VM_PAGE_IN_BYTES].got = false;
u_sess->stat_cxt.osStatDescArray[VM_PAGE_OUT_BYTES].got = false;
if ((fd = open(vmStatPath, O_RDONLY, 0)) >= 0) {
if (read(fd, buffer, VmStatFileReadBuffer) > 0) {
buffer[VmStatFileReadBuffer] = '\0';
temp = strstr(buffer, "pswpin");
if (NULL != temp) {
temp += sizeof("pswpin");
inPages = strtoul(temp, NULL, 10);
if (inPages < ULONG_MAX / pageSize) {
u_sess->stat_cxt.osStatDataArray[VM_PAGE_IN_BYTES].int64Value = inPages * pageSize;
u_sess->stat_cxt.osStatDescArray[VM_PAGE_IN_BYTES].got = true;
}
}
temp = strstr(buffer, "pswpout");
if (NULL != temp) {
temp += sizeof("pswpout");
outPages = strtoul(temp, NULL, 10);
if (outPages < ULONG_MAX / pageSize) {
u_sess->stat_cxt.osStatDataArray[VM_PAGE_OUT_BYTES].int64Value = outPages * pageSize;
u_sess->stat_cxt.osStatDescArray[VM_PAGE_OUT_BYTES].got = true;
}
}
}
close(fd);
}
}
*This function is to get the total physical memory size of the system. We read /proc/meminfo
*file for this infomation. If we failed to get this field, we just ignore it when we report. And if
*if we got this field, we will not read the file when we call this function next time.
*/
void getTotalMem(void)
{
const char* memInfoPath = "/proc/meminfo";
FILE* fd = NULL;
char* line = NULL;
char* temp = NULL;
uint64 ret = 0;
size_t len = 0;
if (u_sess->stat_cxt.osStatDescArray[PHYSICAL_MEMORY_BYTES].got)
return;
if ((fd = fopen(memInfoPath, "r")) != NULL) {
if (gs_getline(&line, &len, fd) > 0) {
temp = line + sizeof("MemTotal:");
ret = strtoul(temp, NULL, 10);
if (ret < ULONG_MAX / 1024) {
u_sess->stat_cxt.osStatDataArray[PHYSICAL_MEMORY_BYTES].int64Value = ret * 1024;
u_sess->stat_cxt.osStatDescArray[PHYSICAL_MEMORY_BYTES].got = true;
}
}
if (NULL != line)
pfree(line);
(void)fclose(fd);
}
}
*This function is to get the load infomation of the system (actually it's the avg length of cpu
*wait queue in the past one minute). We read /proc/loadavg file for this infomation. If we
*failed to get this field, we just ignore it when we report.
*/
void getOSRunLoad(void)
{
const char* loadAvgPath = "/proc/loadavg";
FILE* fd = NULL;
char* line = NULL;
size_t len = 0;
u_sess->stat_cxt.osStatDescArray[RUNLOAD].got = false;
if ((fd = fopen(loadAvgPath, "r")) != NULL) {
if (gs_getline(&line, &len, fd) > 0) {
u_sess->stat_cxt.osStatDataArray[RUNLOAD].float8Value = strtod(line, NULL);
u_sess->stat_cxt.osStatDescArray[RUNLOAD].got = true;
}
if (NULL != line)
pfree(line);
fclose(fd);
}
}
Datum Int64GetNumberDatum(NumericValue value)
{
return DirectFunctionCall1(int8_numeric, Int64GetDatum(value.int64Value));
}
Datum Float8GetNumberDatum(NumericValue value)
{
return DirectFunctionCall1(float8_numeric, Float8GetDatum(value.float8Value));
}
Datum Int32GetNumberDatum(NumericValue value)
{
return DirectFunctionCall1(int4_numeric, Int32GetDatum(value.int32Value));
}
void getSessionID(char* sessid, pg_time_t startTime, ThreadId Threadid)
{
int rc = 0;
Assert(NULL != sessid);
rc = snprintf_s(sessid, SESSION_ID_LEN, SESSION_ID_LEN - 1, "%ld.%lu", startTime, Threadid);
securec_check_ss(rc, "\0", "\0");
}
void getThrdID(char* thrdid, pg_time_t startTime, ThreadId Threadid)
{
int rc = 0;
Assert(NULL != thrdid);
rc = snprintf_s(thrdid, SESSION_ID_LEN, SESSION_ID_LEN - 1, "%ld.%lu", startTime, Threadid);
securec_check_ss(rc, "\0", "\0");
}
#ifdef ENABLE_MOT
MotSessionMemoryDetail* GetMotSessionMemoryDetail(uint32* num)
{
MotSessionMemoryDetail* returnDetailArray = NULL;
ForeignDataWrapper* fdw = NULL;
FdwRoutine* fdwroutine = NULL;
*num = 0;
fdw = GetForeignDataWrapperByName(MOT_FDW, false);
if (fdw != NULL) {
fdwroutine = GetFdwRoutine(fdw->fdwhandler);
if (fdwroutine) {
returnDetailArray = fdwroutine->GetForeignSessionMemSize(num);
}
}
return returnDetailArray;
}
MotMemoryDetail* GetMotMemoryDetail(uint32* num, bool isGlobal)
{
MotMemoryDetail* returnDetailArray = NULL;
ForeignDataWrapper* fdw = NULL;
FdwRoutine* fdwroutine = NULL;
*num = 0;
fdw = GetForeignDataWrapperByName(MOT_FDW, false);
if (fdw != NULL) {
fdwroutine = GetFdwRoutine(fdw->fdwhandler);
if (fdwroutine != NULL) {
returnDetailArray = fdwroutine->GetForeignMemSize(num, isGlobal);
}
}
return returnDetailArray;
}
MotJitDetail* GetMotJitDetail(uint32* num)
{
MotJitDetail* returnDetailArray = NULL;
*num = 0;
if (!u_sess->mot_cxt.callbacks_set) {
ForeignDataWrapper* fdw = GetForeignDataWrapperByName(MOT_FDW, false);
if (fdw != NULL) {
(void)GetFdwRoutine(fdw->fdwhandler);
}
}
returnDetailArray = JitExec::MOTGetJitDetail(num);
return returnDetailArray;
}
MotJitProfile* GetMotJitProfile(uint32* num)
{
MotJitProfile* returnProfileArray = NULL;
*num = 0;
if (!u_sess->mot_cxt.callbacks_set) {
ForeignDataWrapper* fdw = GetForeignDataWrapperByName(MOT_FDW, false);
if (fdw != NULL) {
(void)GetFdwRoutine(fdw->fdwhandler);
}
}
returnProfileArray = JitExec::MOTGetJitProfile(num);
return returnProfileArray;
}
#endif
int64 getCpuTime(void)
{
#ifndef WIN32
struct timespec tv;
int64 res;
(void)clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tv);
res = tv.tv_sec * 1000000 + tv.tv_nsec / 1000;
return res;
#else
return 0;
#endif
}
int64 JiffiesToSec(uint64 jiffies)
{
return JiffiesGetCentiSec(jiffies) / 100;
}
Size sessionTimeShmemSize(void)
{
return mul_size(SessionTimeArraySize, sizeof(SessionTimeEntry));
}
void sessionTimeShmemInit(void)
{
bool found = false;
errno_t rc;
t_thrd.shemem_ptr_cxt.sessionTimeArray = (SessionTimeEntry*)ShmemInitStruct(
"SessionTime Array", mul_size(SessionTimeArraySize, sizeof(SessionTimeEntry)), &found);
if (!found) {
rc = memset_s(t_thrd.shemem_ptr_cxt.sessionTimeArray,
mul_size(SessionTimeArraySize, sizeof(SessionTimeEntry)),
0,
mul_size(SessionTimeArraySize, sizeof(SessionTimeEntry)));
securec_check(rc, "\0", "\0");
}
}
void initMySessionTimeEntry(void)
{
Assert(t_thrd.shemem_ptr_cxt.sessionTimeArray);
if (t_thrd.proc_cxt.MyBackendId != InvalidBackendId) {
Assert(t_thrd.proc_cxt.MyBackendId >= 1 &&
t_thrd.proc_cxt.MyBackendId <= (g_instance.attr.attr_common.enable_thread_pool
? GLOBAL_RESERVE_SESSION_NUM
: g_instance.shmem_cxt.MaxBackends));
t_thrd.shemem_ptr_cxt.mySessionTimeEntry =
&t_thrd.shemem_ptr_cxt.sessionTimeArray[t_thrd.proc_cxt.MyBackendId - 1];
} else {
int index = GetAuxProcStatEntryIndex();
t_thrd.shemem_ptr_cxt.mySessionTimeEntry = &t_thrd.shemem_ptr_cxt.sessionTimeArray[index];
}
do {
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount++;
} while ((t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount & 1) == 0);
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->isActive = true;
if (!ENABLE_THREAD_POOL)
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->sessionid = t_thrd.proc_cxt.MyProcPid;
else {
if (IS_THREAD_POOL_WORKER && u_sess->session_id > 0)
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->sessionid = u_sess->session_id;
else
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->sessionid = t_thrd.proc_cxt.MyProcPid;
}
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->myStartTime = t_thrd.proc_cxt.MyStartTime;
errno_t rc = memset_s((int64*)t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array,
sizeof(t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array),
0,
sizeof(t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array));
securec_check(rc, "\0", "\0");
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount++;
Assert((t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount & 1) == 0);
on_shmem_exit(endMySessionTimeEntry, 0);
}
static void DetachMySessionTimeEntry(volatile SessionTimeEntry* pEntry)
{
LWLockAcquire(InstanceTimeLock, LW_EXCLUSIVE);
pEntry->changeCount++;
for (uint32 idx = 0; idx < TOTAL_TIME_INFO_TYPES; idx++) {
g_instance.stat_cxt.gInstanceTimeInfo[idx] += pEntry->array[idx];
}
pEntry->isActive = false;
pEntry->changeCount++;
Assert((pEntry->changeCount & 1) == 0);
LWLockRelease(InstanceTimeLock);
}
* mark t_thrd.shemem_ptr_cxt.mySessionTimeEntry closed when we shut down.
*/
static void endMySessionTimeEntry(int code, Datum arg)
{
DetachMySessionTimeEntry(t_thrd.shemem_ptr_cxt.mySessionTimeEntry);
if (IS_THREAD_POOL_WORKER && t_thrd.proc_cxt.MyBackendId != InvalidBackendId &&
t_thrd.shemem_ptr_cxt.mySessionTimeEntry !=
&t_thrd.shemem_ptr_cxt.sessionTimeArray[t_thrd.proc_cxt.MyBackendId - 1]) {
DetachMySessionTimeEntry(&t_thrd.shemem_ptr_cxt.sessionTimeArray[t_thrd.proc_cxt.MyBackendId - 1]);
}
t_thrd.shemem_ptr_cxt.mySessionTimeEntry = NULL;
}
* attach mySessionTimeEntry to session entry
*/
void AttachMySessionTimeEntry(void)
{
Assert(t_thrd.shemem_ptr_cxt.sessionTimeArray);
Assert(u_sess->session_ctr_index >= GLOBAL_RESERVE_SESSION_NUM && u_sess->session_ctr_index < MAX_BACKEND_SLOT);
Assert(t_thrd.proc_cxt.MyBackendId != InvalidBackendId);
Assert(t_thrd.shemem_ptr_cxt.mySessionTimeEntry ==
&t_thrd.shemem_ptr_cxt.sessionTimeArray[t_thrd.proc_cxt.MyBackendId - 1]);
t_thrd.shemem_ptr_cxt.mySessionTimeEntry = &t_thrd.shemem_ptr_cxt.sessionTimeArray[u_sess->session_ctr_index];
do {
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount++;
} while ((t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount & 1) == 0);
if (t_thrd.shemem_ptr_cxt.mySessionTimeEntry->isActive == false) {
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->myStartTime =
timestamptz_to_time_t(u_sess->proc_cxt.MyProcPort->SessionStartTime);
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->sessionid = u_sess->session_id;
errno_t rc = memset_s((int64*)t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array,
sizeof(t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array),
0,
sizeof(t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array));
securec_check(rc, "\0", "\0");
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->isActive = true;
}
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount++;
Assert((t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount & 1) == 0);
}
static void addThreadTimeEntry()
{
for (int i = 0; i < TOTAL_TIME_INFO_TYPES; i++) {
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array[i] +=
u_sess->stat_cxt.localTimeInfoArray[i];
}
}
void ResetMemory(void* dest, size_t size)
{
errno_t rc;
rc = memset_s(dest, size, 0, size);
securec_check(rc, "\0", "\0");
}
bool nettime_trace_is_working()
{
return u_sess->statement_cxt.remote_support_trace && u_sess->attr.attr_common.enable_record_nettime;
}
void timeInfoRecordStart(void)
{
if (!og_time_record_start()) {
return;
}
if (u_sess->attr.attr_common.enable_instr_cpu_timer)
u_sess->stat_cxt.localTimeInfoArray[CPU_TIME] = getCpuTime();
}
void timeInfoRecordEnd(bool update_delay)
{
if (!og_time_record_is_started()) {
return;
}
if (u_sess->attr.attr_common.enable_instr_cpu_timer) {
int64 cur = getCpuTime();
u_sess->stat_cxt.localTimeInfoArray[CPU_TIME] = cur -
u_sess->stat_cxt.localTimeInfoArray[CPU_TIME];
}
og_time_record_end();
og_get_record_stat()->print_self();
if (u_sess->statement_cxt.nettime_trace_is_working && CURRENT_STMT_METRIC_HANDLE) {
u_sess->statement_cxt.total_db_time += u_sess->stat_cxt.localTimeInfoArray[DB_TIME];
}
if (!update_delay) {
update_sql_state();
}
}
void update_sql_state(void) {
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount++;
addThreadTimeEntry();
t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount++;
Assert((t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount & 1) == 0);
UniqueSQLStat sqlStat;
sqlStat.timeInfo = u_sess->stat_cxt.localTimeInfoArray;
sqlStat.netInfo = u_sess->stat_cxt.localNetInfo;
if (u_sess->unique_sql_cxt.unique_sql_id != 0 && is_unique_sql_enabled()) {
UpdateUniqueSQLStat(NULL, NULL, 0, NULL, &sqlStat);
}
ResetMemory(u_sess->stat_cxt.localTimeInfoArray,
sizeof(int64) * TOTAL_TIME_INFO_TYPES);
ResetMemory(u_sess->stat_cxt.localNetInfo,
sizeof(uint64) * TOTAL_NET_INFO_TYPES);
}
void getSessionTimeStatus(Tuplestorestate *tupStore, TupleDesc tupDesc,
void (*insert)(Tuplestorestate *tupStore, TupleDesc tupDesc, const SessionTimeEntry *entry))
{
SessionTimeEntry *localEntry = NULL;
SessionTimeEntry *entry = NULL;
int entryIndex;
localEntry = (SessionTimeEntry *)palloc0(sizeof(SessionTimeEntry));
for (entryIndex = 0; entryIndex < SessionTimeArraySize; entryIndex++) {
entry = &(t_thrd.shemem_ptr_cxt.sessionTimeArray[entryIndex]);
READ_AN_ENTRY(localEntry, entry, entry->changeCount, SessionTimeEntry);
if (!localEntry->isActive)
continue;
insert(tupStore, tupDesc, localEntry);
}
pfree(localEntry);
}
SessionTimeEntry* getInstanceTimeStatus()
{
SessionTimeEntry* retEntry = NULL;
SessionTimeEntry localEntry;
SessionTimeEntry* entry = NULL;
int entryIndex, idx;
retEntry = (SessionTimeEntry*)palloc0(sizeof(SessionTimeEntry));
LWLockAcquire(InstanceTimeLock, LW_SHARED);
for (entryIndex = 0; entryIndex < SessionTimeArraySize; entryIndex++) {
entry = &(t_thrd.shemem_ptr_cxt.sessionTimeArray[entryIndex]);
if (entry->isActive) {
READ_AN_ENTRY(&localEntry, entry, entry->changeCount, SessionTimeEntry);
for (idx = 0; idx < TOTAL_TIME_INFO_TYPES; idx++)
retEntry->array[idx] += localEntry.array[idx];
}
}
for (idx = 0; idx < TOTAL_TIME_INFO_TYPES; idx++)
retEntry->array[idx] += g_instance.stat_cxt.gInstanceTimeInfo[idx];
LWLockRelease(InstanceTimeLock);
return retEntry;
}
PgStat_RedoEntry redoStatistics;
void reportRedoWrite(PgStat_Counter blks, PgStat_Counter tim)
{
PgStat_RedoEntry* entry = &redoStatistics;
if (0 >= blks) {
return;
}
entry->writes++;
entry->writeBlks += blks;
entry->writeTime += tim;
entry->avgIOTime = entry->writeTime / entry->writes;
entry->lstIOTime = tim;
if (entry->minIOTime > tim || 0 == entry->minIOTime) {
entry->minIOTime = tim;
}
if (entry->maxIOTime < tim) {
entry->maxIOTime = tim;
}
}
static void endMySessionStatEntry(int code, Datum arg);
Size sessionStatShmemSize(void)
{
return mul_size(sizeof(SessionLevelStatistic), SessionStatArraySize);
}
void sessionStatShmemInit(void)
{
bool found = false;
errno_t rc;
t_thrd.shemem_ptr_cxt.sessionStatArray =
(SessionLevelStatistic*)ShmemInitStruct("Session Statistic Array", sessionStatShmemSize(), &found);
if (!found) {
rc = memset_s(t_thrd.shemem_ptr_cxt.sessionStatArray, sessionStatShmemSize(), 0, sessionStatShmemSize());
securec_check(rc, "\0", "\0");
}
}
void initMySessionStatEntry(void)
{
Assert(t_thrd.shemem_ptr_cxt.sessionStatArray);
if (t_thrd.proc_cxt.MyBackendId != InvalidBackendId) {
Assert(t_thrd.proc_cxt.MyBackendId >= 1 &&
t_thrd.proc_cxt.MyBackendId <= (g_instance.attr.attr_common.enable_thread_pool
? GLOBAL_RESERVE_SESSION_NUM
: g_instance.shmem_cxt.MaxBackends));
t_thrd.shemem_ptr_cxt.mySessionStatEntry =
&t_thrd.shemem_ptr_cxt.sessionStatArray[t_thrd.proc_cxt.MyBackendId - 1];
} else {
int index = GetAuxProcStatEntryIndex();
t_thrd.shemem_ptr_cxt.mySessionStatEntry = &t_thrd.shemem_ptr_cxt.sessionStatArray[index];
}
errno_t rc = memset_s((SessionLevelStatistic*)t_thrd.shemem_ptr_cxt.mySessionStatEntry,
sizeof(SessionLevelStatistic),
0,
sizeof(SessionLevelStatistic));
securec_check(rc, "\0", "\0");
t_thrd.shemem_ptr_cxt.mySessionStatEntry->sessionStartTime = t_thrd.proc_cxt.MyStartTime;
if (!ENABLE_THREAD_POOL)
t_thrd.shemem_ptr_cxt.mySessionStatEntry->sessionid = t_thrd.proc_cxt.MyProcPid;
else {
if (IS_THREAD_POOL_WORKER && u_sess->session_id > 0)
t_thrd.shemem_ptr_cxt.mySessionStatEntry->sessionid = u_sess->session_id;
else
t_thrd.shemem_ptr_cxt.mySessionStatEntry->sessionid = t_thrd.proc_cxt.MyProcPid;
}
t_thrd.shemem_ptr_cxt.mySessionStatEntry->isValid = true;
on_shmem_exit(endMySessionStatEntry, 0);
}
static void endMySessionStatEntry(int code, Datum arg)
{
t_thrd.shemem_ptr_cxt.mySessionStatEntry->isValid = false;
if (IS_THREAD_POOL_WORKER && t_thrd.proc_cxt.MyBackendId != InvalidBackendId &&
t_thrd.shemem_ptr_cxt.mySessionStatEntry !=
&t_thrd.shemem_ptr_cxt.sessionStatArray[t_thrd.proc_cxt.MyBackendId - 1]) {
t_thrd.shemem_ptr_cxt.sessionStatArray[t_thrd.proc_cxt.MyBackendId - 1].isValid = false;
}
t_thrd.shemem_ptr_cxt.mySessionStatEntry = NULL;
}
* attach mySessionStatEntry to session entry
*/
void AttachMySessionStatEntry(void)
{
Assert(t_thrd.shemem_ptr_cxt.sessionStatArray);
Assert(u_sess->session_ctr_index >= GLOBAL_RESERVE_SESSION_NUM && u_sess->session_ctr_index < MAX_BACKEND_SLOT);
Assert(t_thrd.proc_cxt.MyBackendId != InvalidBackendId);
Assert(t_thrd.shemem_ptr_cxt.mySessionStatEntry ==
&t_thrd.shemem_ptr_cxt.sessionStatArray[t_thrd.proc_cxt.MyBackendId - 1]);
t_thrd.shemem_ptr_cxt.mySessionStatEntry = &t_thrd.shemem_ptr_cxt.sessionStatArray[u_sess->session_ctr_index];
if (t_thrd.shemem_ptr_cxt.mySessionStatEntry->isValid == false) {
t_thrd.shemem_ptr_cxt.mySessionStatEntry->sessionStartTime =
timestamptz_to_time_t(u_sess->proc_cxt.MyProcPort->SessionStartTime);
t_thrd.shemem_ptr_cxt.mySessionStatEntry->sessionid = u_sess->session_id;
t_thrd.shemem_ptr_cxt.mySessionStatEntry->isValid = true;
}
}
void getSessionStatistics(Tuplestorestate* tupStore, TupleDesc tupDesc,
void (* insert)(Tuplestorestate* tupStore, TupleDesc tupDesc, const SessionLevelStatistic* entry))
{
SessionLevelStatistic* entry = NULL;
SessionLevelStatistic* localEntry = NULL;
int entryIndex;
errno_t rc;
localEntry = (SessionLevelStatistic*)palloc0(sizeof(SessionLevelStatistic));
for (entryIndex = 0; entryIndex < SessionStatArraySize; entryIndex++) {
entry = &(t_thrd.shemem_ptr_cxt.sessionStatArray[entryIndex]);
rc = memcpy_s(localEntry, sizeof(SessionLevelStatistic), entry, sizeof(SessionLevelStatistic));
securec_check(rc, "\0", "\0");
if (!localEntry->isValid)
continue;
insert(tupStore, tupDesc, localEntry);
}
pfree(localEntry);
}
PgStat_FileEntry pgStatFileArray[NUM_FILES];
uint32 fileStatCount = 0;
void reportFileStat(PgStat_MsgFile* msg)
{
pgstat_setheader(&(msg->m_hdr), PGSTAT_MTYPE_FILE);
pgstat_send(msg, sizeof(PgStat_MsgFile));
}
static void pgstat_recv_filestat(PgStat_MsgFile* msg, int len)
{
PgStat_FileEntry* entry = NULL;
int i;
errno_t rc;
if (0 >= msg->blks) {
return;
}
LWLockAcquire(FileStatLock, LW_SHARED);
for (i = 0; i < NUM_FILES; i++) {
entry = (PgStat_FileEntry*)&pgStatFileArray[i];
if (entry->fn == msg->fn || entry->fn == InvalidOid) {
break;
}
}
LWLockRelease(FileStatLock);
if (i == NUM_FILES || entry->fn == InvalidOid) {
LWLockAcquire(FileStatLock, LW_EXCLUSIVE);
if (i == NUM_FILES) {
TimestampTz longestTime = GetCurrentTimestamp();
int minLocation = 0;
for (int j = 0; j < NUM_FILES; j++) {
entry = (PgStat_FileEntry*)&pgStatFileArray[j];
if (entry->time < longestTime) {
longestTime = entry->time;
minLocation = j;
}
}
fileStatCount = minLocation;
ereport(DEBUG1, (errmodule(MOD_INSTR),
errmsg("the latest filenum is %d, update is %ld",
fileStatCount, pgStatFileArray[fileStatCount].time)));
}
entry = (PgStat_FileEntry*)&pgStatFileArray[fileStatCount];
rc = memset_s(entry, sizeof(PgStat_FileEntry), 0, sizeof(PgStat_FileEntry));
securec_check(rc, "\0", "\0");
entry->dbid = msg->dbid;
entry->spcid = msg->spcid;
entry->fn = msg->fn;
fileStatCount = (fileStatCount + 1) % NUM_FILES;
LWLockRelease(FileStatLock);
}
entry->changeCount++;
entry->time = GetCurrentTimestamp();
g_instance.stat_cxt.fileIOStat->changeCount++;
if ('r' == msg->rw) {
entry->reads += msg->cnt;
entry->readBlks += msg->blks;
entry->readTime += msg->tim;
g_instance.stat_cxt.fileIOStat->reads += msg->cnt;
g_instance.stat_cxt.fileIOStat->readBlks += msg->blks;
} else {
entry->writes += msg->cnt;
entry->writeBlks += msg->blks;
entry->writeTime += msg->tim;
g_instance.stat_cxt.fileIOStat->writes += msg->cnt;
g_instance.stat_cxt.fileIOStat->writeBlks += msg->blks;
}
g_instance.stat_cxt.fileIOStat->changeCount++;
if ((entry->reads + entry->writes) <= 0) {
entry->avgIOTime = 0;
} else {
entry->avgIOTime = (entry->readTime + entry->writeTime) / (entry->reads + entry->writes);
}
entry->lstIOTime = msg->lsttim;
if (entry->minIOTime > msg->mintim || 0 == entry->minIOTime) {
entry->minIOTime = msg->mintim;
}
if (entry->maxIOTime < msg->maxtim) {
entry->maxIOTime = msg->maxtim;
}
entry->changeCount++;
return;
}
PgBackendStatus* GetMyBEEntry(void)
{
return t_thrd.shemem_ptr_cxt.MyBEEntry;
}
static void endMySessionMemoryEntry(int code, Datum arg);
Size sessionMemoryShmemSize(void)
{
return mul_size(sizeof(SessionLevelMemory), SessionMemoryArraySize);
}
void sessionMemoryShmemInit(void)
{
bool found = false;
errno_t rc;
t_thrd.shemem_ptr_cxt.sessionMemoryArray =
(SessionLevelMemory*)ShmemInitStruct("Session Memory Array", sessionMemoryShmemSize(), &found);
if (!found) {
rc = memset_s(t_thrd.shemem_ptr_cxt.sessionMemoryArray, sessionMemoryShmemSize(), 0, sessionMemoryShmemSize());
securec_check(rc, "\0", "\0");
}
}
void initMySessionMemoryEntry(void)
{
Assert(t_thrd.shemem_ptr_cxt.sessionMemoryArray);
if (t_thrd.proc_cxt.MyBackendId != InvalidBackendId) {
Assert(t_thrd.proc_cxt.MyBackendId >= 1 &&
t_thrd.proc_cxt.MyBackendId <= (g_instance.attr.attr_common.enable_thread_pool
? GLOBAL_RESERVE_SESSION_NUM
: g_instance.shmem_cxt.MaxBackends));
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry =
&t_thrd.shemem_ptr_cxt.sessionMemoryArray[t_thrd.proc_cxt.MyBackendId - 1];
} else {
int index = GetAuxProcStatEntryIndex();
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry = &t_thrd.shemem_ptr_cxt.sessionMemoryArray[index];
}
errno_t rc = EOK;
rc =
memset_s(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry, sizeof(SessionLevelMemory), 0, sizeof(SessionLevelMemory));
securec_check(rc, "\0", "\0");
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->threadStartTime = t_thrd.proc_cxt.MyStartTime;
if (!ENABLE_THREAD_POOL)
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->sessionid = t_thrd.proc_cxt.MyProcPid;
else {
if (IS_THREAD_POOL_WORKER && u_sess->session_id > 0)
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->sessionid = u_sess->session_id;
else
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->sessionid = t_thrd.proc_cxt.MyProcPid;
}
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->queryMemInChunks = t_thrd.utils_cxt.trackedMemChunks;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->initMemInChunks = t_thrd.utils_cxt.trackedMemChunks;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->peakChunksQuery = t_thrd.utils_cxt.trackedMemChunks;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->spillCount = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->spillSize = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->broadcastSize = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->estimate_time = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->estimate_memory = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->warning = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->query_plan = NULL;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->plan_size = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->query_plan_issue = NULL;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->dnStartTime = GetCurrentTimestamp();
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->dnEndTime = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->isValid = true;
on_shmem_exit(endMySessionMemoryEntry, 0);
}
static void endMySessionMemoryEntry(int code, Datum arg)
{
pgstat_release_session_memory_entry();
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->isValid = false;
if (IS_THREAD_POOL_WORKER && t_thrd.proc_cxt.MyBackendId != InvalidBackendId &&
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry !=
&t_thrd.shemem_ptr_cxt.sessionMemoryArray[t_thrd.proc_cxt.MyBackendId - 1]) {
t_thrd.shemem_ptr_cxt.sessionMemoryArray[t_thrd.proc_cxt.MyBackendId - 1].isValid = false;
}
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry = NULL;
}
* attach mySessionMemoryEntry to session entry
*/
void AttachMySessionMemoryEntry(void)
{
Assert(t_thrd.shemem_ptr_cxt.sessionMemoryArray);
Assert(u_sess->session_ctr_index >= GLOBAL_RESERVE_SESSION_NUM && u_sess->session_ctr_index < MAX_BACKEND_SLOT);
Assert(t_thrd.proc_cxt.MyBackendId != InvalidBackendId);
Assert(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry ==
&t_thrd.shemem_ptr_cxt.sessionMemoryArray[t_thrd.proc_cxt.MyBackendId - 1]);
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry = &t_thrd.shemem_ptr_cxt.sessionMemoryArray[u_sess->session_ctr_index];
if (t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->isValid == false) {
errno_t rc = EOK;
rc = memset_s(
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry, sizeof(SessionLevelMemory), 0, sizeof(SessionLevelMemory));
securec_check(rc, "\0", "\0");
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->threadStartTime =
timestamptz_to_time_t(u_sess->proc_cxt.MyProcPort->SessionStartTime);
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->sessionid = u_sess->session_id;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->queryMemInChunks = t_thrd.utils_cxt.trackedMemChunks;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->initMemInChunks = t_thrd.utils_cxt.trackedMemChunks;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->peakChunksQuery = t_thrd.utils_cxt.trackedMemChunks;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->spillCount = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->spillSize = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->broadcastSize = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->estimate_time = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->estimate_memory = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->warning = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->query_plan = NULL;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->plan_size = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->query_plan_issue = NULL;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->dnStartTime = GetCurrentTimestamp();
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->dnEndTime = 0;
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->isValid = true;
}
t_thrd.proc->sessMemorySessionid = u_sess->session_id;
}
void getSessionMemory(Tuplestorestate* tupStore, TupleDesc tupDesc,
void (* insert)(Tuplestorestate* tupStore, TupleDesc tupDesc, const SessionLevelMemory* entry))
{
SessionLevelMemory* localEntry = NULL;
SessionLevelMemory* entry = NULL;
int entryIndex;
errno_t rc;
localEntry = (SessionLevelMemory*)palloc0(sizeof(SessionLevelMemory));
for (entryIndex = 0; entryIndex < SessionMemoryArraySize; entryIndex++) {
entry = &(t_thrd.shemem_ptr_cxt.sessionMemoryArray[entryIndex]);
rc = memcpy_s(localEntry, sizeof(SessionLevelMemory), entry, sizeof(SessionLevelMemory));
securec_check(rc, "\0", "\0");
if (false == localEntry->isValid || 0 == (localEntry->peakChunksQuery - localEntry->initMemInChunks))
continue;
insert(tupStore, tupDesc, localEntry);
}
pfree(localEntry);
}
typedef AllocSetContext* AllocSet;
static void calculateThreadMemoryContextStats(const volatile PGPROC* proc, const MemoryContext context, bool isShared,
Tuplestorestate* tupStore, TupleDesc tupDesc)
{
AllocSet set = (AllocSet)context;
if (!isShared) {
char sessId[SESSION_ID_LEN] = {0};
ThreadId threadId;
char threadType[PROC_NAME_LEN] = {0};
errno_t rc;
if (proc != NULL) {
threadId = proc->pid;
rc = strncpy_s(threadType,
PROC_NAME_LEN,
(proc->myProgName[0] != '\0') ? (const char*)proc->myProgName : "",
PROC_NAME_LEN - 1);
securec_check(rc, "\0", "\0");
getSessionID(sessId, proc->myStartTime, threadId);
} else {
threadId = PostmasterPid;
rc = strncpy_s(threadType, PROC_NAME_LEN, "postmaster", PROC_NAME_LEN - 1);
securec_check(rc, "\0", "\0");
getSessionID(sessId, (pg_time_t)0, threadId);
}
Datum values[NUM_THREAD_MEMORY_DETAIL_ELEM] = {0};
bool nulls[NUM_THREAD_MEMORY_DETAIL_ELEM] = {false};
values[0] = CStringGetTextDatum(sessId);
values[1] = Int64GetDatum(threadId);
values[2] = CStringGetTextDatum(threadType);
values[3] = CStringGetTextDatum(context->name);
values[4] = Int16GetDatum(context->level);
if (context->level > 0 && context->parent != NULL)
values[5] = CStringGetTextDatum(context->parent->name);
else
nulls[5] = true;
values[6] = Int64GetDatum(set->totalSpace);
values[7] = Int64GetDatum(set->freeSpace);
values[8] = Int64GetDatum(set->totalSpace - set->freeSpace);
tuplestore_putvalues(tupStore, tupDesc, values, nulls);
} else {
Datum values[NUM_SHARED_MEMORY_DETAIL_ELEM] = {0};
bool nulls[NUM_SHARED_MEMORY_DETAIL_ELEM] = {false};
values[0] = CStringGetTextDatum(context->name);
values[1] = Int16GetDatum(context->level);
if (context->level > 0 && context->parent != NULL)
values[2] = CStringGetTextDatum(context->parent->name);
else
nulls[2] = true;
values[3] = Int64GetDatum(set->totalSpace);
values[4] = Int64GetDatum(set->freeSpace);
values[5] = Int64GetDatum(set->totalSpace - set->freeSpace);
tuplestore_putvalues(tupStore, tupDesc, values, nulls);
}
}
static void recursiveThreadMemoryContext(const volatile PGPROC* proc, const MemoryContext context, bool isShared,
Tuplestorestate* tupStore, TupleDesc tupDesc)
{
MemoryContext child;
bool checkLock = false;
PG_TRY();
{
CHECK_FOR_INTERRUPTS();
if (isShared) {
MemoryContextLock(context);
checkLock = true;
}
calculateThreadMemoryContextStats(proc, context, isShared, tupStore, tupDesc);
for (child = context->firstchild; child != NULL; child = child->nextchild) {
if (child->is_shared == isShared) {
recursiveThreadMemoryContext(proc, child, isShared, tupStore, tupDesc);
}
}
}
PG_CATCH();
{
if (isShared && checkLock) {
MemoryContextUnlock(context);
}
PG_RE_THROW();
}
PG_END_TRY();
if (isShared) {
MemoryContextUnlock(context);
}
}
* @@GaussDB@@
* Target : pv_shared_memory_detail view
* Brief :
* Description :
*/
void getSharedMemoryDetail(Tuplestorestate* tupStore, TupleDesc tupDesc)
{
recursiveThreadMemoryContext(NULL, g_instance.instance_context, true, tupStore, tupDesc);
}
* @@GaussDB@@
* Target : pv_thread_memory_detail view
* Brief :
* Description :
* Notes :
* Author :
*/
void getThreadMemoryDetail(Tuplestorestate* tupStore, TupleDesc tupDesc, uint32* procIdx)
{
uint32 max_thread_count = g_instance.proc_base->allProcCount -
g_instance.attr.attr_storage.max_prepared_xacts * NUM_TWOPHASE_PARTITIONS;
volatile PGPROC* proc = NULL;
uint32 idx = 0;
PG_TRY();
{
HOLD_INTERRUPTS();
for (idx = 0; idx < max_thread_count; idx++) {
proc = g_instance.proc_base_all_procs[idx];
*procIdx = idx;
(void)syscalllockAcquire(&((PGPROC*)proc)->deleMemContextMutex);
if (proc->topmcxt != NULL)
recursiveThreadMemoryContext(proc, proc->topmcxt, false, tupStore, tupDesc);
(void)syscalllockRelease(&((PGPROC*)proc)->deleMemContextMutex);
}
if (IsNormalProcessingMode())
recursiveThreadMemoryContext(NULL, PmTopMemoryContext, false, tupStore, tupDesc);
RESUME_INTERRUPTS();
}
PG_CATCH();
{
if (*procIdx < max_thread_count) {
proc = g_instance.proc_base_all_procs[*procIdx];
(void)syscalllockRelease(&((PGPROC*)proc)->deleMemContextMutex);
}
PG_RE_THROW();
}
PG_END_TRY();
}
#ifdef MEMORY_CONTEXT_CHECKING
extern void dumpAllocBlock(AllocSet set, StringInfoData* buf);
extern void dumpAsanBlock(AsanSet set, StringInfoData* buf);
static void recursiveMemoryContextForDump(const MemoryContext context, char* ctx_name, StringInfoData* buf)
{
#ifndef ENABLE_MEMORY_CHECK
if (!strcmp(ctx_name, context->name) &&
(context->type == T_AllocSetContext || context->type == T_SharedAllocSetContext))
dumpAllocBlock((AllocSet)context, buf);
#else
if ((context->type == T_AsanSetContext || context->type == T_SharedAllocSetContext)) {
appendStringInfo(buf, "context : %s\n", context->name);
dumpAsanBlock((AsanSet)context, buf);
}
#endif
for (MemoryContext child = context->firstchild; child != NULL; child = child->nextchild) {
recursiveMemoryContextForDump(child, ctx_name, buf);
}
return;
}
void DumpMemoryContext(DUMP_TYPE type)
{
#define DUMPFILE_BUFF_SIZE 128
char ctx_name[MEMORY_CONTEXT_NAME_LEN] = {0};
char dump_dir[MAX_PATH_LEN] = {0};
errno_t ss_rc;
bool is_absolute = false;
is_absolute = is_absolute_path(u_sess->attr.attr_common.Log_directory);
if (is_absolute) {
ss_rc = snprintf_s(
dump_dir, sizeof(dump_dir), sizeof(dump_dir) - 1, "%s/memdump", u_sess->attr.attr_common.Log_directory);
securec_check_ss(ss_rc, "\0", "\0");
} else {
ss_rc = snprintf_s(dump_dir,
sizeof(dump_dir),
sizeof(dump_dir) - 1,
"%s/pg_log/memdump",
g_instance.attr.attr_common.data_directory);
securec_check_ss(ss_rc, "\0", "\0");
}
ss_rc = strcpy_s(ctx_name, MEMORY_CONTEXT_NAME_LEN, dump_memory_context_name);
securec_check(ss_rc, "\0", "\0");
int ret;
struct stat info;
if (stat(dump_dir, &info) == 0) {
if (!S_ISDIR(info.st_mode)) {
elog(LOG, "%s maybe not a directory.", dump_dir);
return;
}
} else {
ret = mkdir(dump_dir, S_IRWXU);
if (ret < 0) {
elog(LOG, "Fail to create %s", dump_dir);
return;
}
}
char dump_file[MAX_PATH_LEN] = {0};
#ifdef ENABLE_MEMORY_CHECK
pid_t pid = getpid();
int rc = sprintf_s(dump_file,
MAX_PATH_LEN,
"%s/%s_%s_%d.log",
dump_dir,
g_instance.attr.attr_common.PGXCNodeName,
g_instance.instance_context->name,
pid);
#else
ThreadId tid = gs_thread_self();
int rc = sprintf_s(
dump_file, MAX_PATH_LEN, "%s/%s_%lu_%lu.log", dump_dir, ctx_name, (unsigned long)tid, (uint64)time(NULL));
#endif
securec_check_ss(rc, "\0", "\0");
FILE* dump_fp = fopen(dump_file, "w");
if (NULL == dump_fp) {
elog(LOG, "dump_memory: Failed to create file: %s, cause: %s", dump_file, strerror(errno));
return;
}
MemoryContext ctx;
switch (type) {
case STANDARD_DUMP:
ctx = t_thrd.top_mem_cxt;
break;
case SHARED_DUMP:
ctx = g_instance.instance_context;
break;
default:
elog(LOG, "dump_memory: invalid dump type: %d", type);
fclose(dump_fp);
return;
}
PG_TRY();
{
StringInfoData memBuf;
initStringInfo(&memBuf);
recursiveMemoryContextForDump(ctx, ctx_name, &memBuf);
uint64 bytes = fwrite(memBuf.data, 1, memBuf.len, dump_fp);
if (bytes != (uint64)memBuf.len) {
elog(LOG, "Could not write memory usage information. Attempted to write %d", memBuf.len);
}
pfree(memBuf.data);
}
PG_CATCH();
{
fclose(dump_fp);
return;
}
PG_END_TRY();
fclose(dump_fp);
return;
}
#endif
static void FetchLWLockInfoOfAllBackends(lwm_lwlocks** lwlockInfo, int& nBackends)
{
nBackends = BackendStatusArray_size;
*lwlockInfo = (lwm_lwlocks*)palloc0(sizeof(lwm_lwlocks) * nBackends);
lwm_lwlocks* lwlock = *lwlockInfo;
for (int n = 0; n < nBackends; ++n) {
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + n;
START_CRIT_SECTION();
beentry->st_lw_access_flag = true;
pg_memory_barrier();
if (beentry->st_lw_is_cleanning_flag || beentry->st_procpid == 0) {
beentry->st_lw_access_flag = false;
END_CRIT_SECTION();
++beentry;
continue;
}
for (;;) {
int before_changecount;
int after_changecount;
pgstat_save_changecount_before(beentry, before_changecount);
if (beentry->lw_held_num == NULL) {
break;
}
lwlock->lwlocks_num = *(beentry->lw_held_num);
if (lwlock->lwlocks_num > 0) {
lwlock->be_tid.thread_id = beentry->st_procpid;
lwlock->be_tid.st_sessionid = beentry->st_sessionid;
lwlock->want_lwlock.lock = beentry->lw_want_lock;
const int needSize = (sizeof(lwlock_id_mode) * lwlock->lwlocks_num);
if (lwlock->held_lwlocks != NULL) {
lwlock->held_lwlocks = (lwlock_id_mode*)repalloc(lwlock->held_lwlocks, needSize);
} else {
lwlock->held_lwlocks = (lwlock_id_mode*)palloc0(needSize);
}
copy_held_lwlocks(beentry->lw_held_locks, lwlock->held_lwlocks, lwlock->lwlocks_num);
pgstat_save_changecount_after(beentry, after_changecount);
if (before_changecount == after_changecount && (before_changecount & 1) == 0) {
++lwlock;
break;
}
} else {
break;
}
}
pg_memory_barrier();
beentry->st_lw_access_flag = false;
END_CRIT_SECTION();
++beentry;
}
nBackends = (lwlock - *lwlockInfo);
}
static void OutLogLWLockInfoOfAllBackends(lwm_lwlocks* lwlockInfo, int nBackends)
{
StringInfoData fmtOutLog;
initStringInfo(&fmtOutLog);
lwm_lwlocks* info = lwlockInfo;
for (int beIdx = 0; beIdx < nBackends; ++beIdx) {
appendStringInfo(&fmtOutLog,
"[LWLOCK INFO] thread %lu, required (%s), held num %d, locks(name, mode):",
info->be_tid.thread_id,
T_NAME(info->want_lwlock.lock),
info->lwlocks_num);
for (int lockIdx = 0; lockIdx < info->lwlocks_num; ++lockIdx) {
appendStringInfo(&fmtOutLog,
"(%s, %d)",
T_NAME(info->held_lwlocks[lockIdx].lock_addr.lock),
info->held_lwlocks[lockIdx].lock_sx);
}
ereport(LOG, (errmsg("%s", fmtOutLog.data)));
resetStringInfo(&fmtOutLog);
++info;
}
pfree(fmtOutLog.data);
}
static void FreeLWLockInfoOfAllBackends(lwm_lwlocks* lwlockInfo, int nBackends)
{
lwm_lwlocks* info = lwlockInfo;
for (int i = 0; i < nBackends; ++i) {
pfree(info->held_lwlocks);
++info;
}
pfree(lwlockInfo);
}
void DumpLWLockInfoToServerLog(void)
{
lwm_lwlocks* lwlockInfo = NULL;
int nBackends = 0;
FetchLWLockInfoOfAllBackends(&lwlockInfo, nBackends);
OutLogLWLockInfoOfAllBackends(lwlockInfo, nBackends);
FreeLWLockInfoOfAllBackends(lwlockInfo, nBackends);
}
* read light-weight detect data from backends data.
*/
lwm_light_detect* pgstat_read_light_detect(void)
{
const int n = BackendStatusArray_size;
lwm_light_detect* lw_detect = (lwm_light_detect*)palloc0(sizeof(lwm_light_detect) * n);
lwm_light_detect* lw_det = lw_detect;
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray;
for (int i = 0; i < n; i++) {
lwm_light_detect ld = {0, 0};
for (;;) {
int before_changecount;
int after_changecount;
pgstat_save_changecount_before(beentry, before_changecount);
if (beentry->st_procpid > 0 || beentry->st_sessionid > 0) {
ld.entry_id.thread_id = beentry->st_procpid;
ld.entry_id.st_sessionid = beentry->st_sessionid;
ld.lw_count = beentry->lw_count;
}
pgstat_save_changecount_after(beentry, after_changecount);
if (before_changecount == after_changecount && (before_changecount & 1) == 0) {
break;
}
}
if (ld.entry_id.thread_id != 0 && CHANGECOUNT_IS_EVEN(ld.lw_count)) {
* remember the two info only if both the following hold
* 1) thread id is valid means that backend is running.
* 2) fast count is even means that backend is waiting lwlock.
*/
lw_det->entry_id.thread_id = ld.entry_id.thread_id;
lw_det->entry_id.st_sessionid = ld.entry_id.st_sessionid;
lw_det->lw_count = ld.lw_count;
}
++beentry;
++lw_det;
}
return lw_detect;
}
* read diagnosis data according to the positions and number of candidates.
* these diagnosis information includes: 1) lwlock to require; 2) thread id;
* 3) the number and id of lwlocks held by this thread;
*/
lwm_lwlocks* pgstat_read_diagnosis_data(lwm_light_detect* light_det, const int* candidates_pos, int num_candidates)
{
Size need_size = sizeof(lwm_lwlocks) * (uint32)num_candidates;
lwm_lwlocks* lwlocks = (lwm_lwlocks*)palloc0(need_size);
lwm_lwlocks* cur_lwlock = NULL;
lwm_lwlocks tmplock;
errno_t errval = memset_s(&tmplock, sizeof(tmplock), 0, sizeof(tmplock));
securec_check_errval(errval, , LOG);
cur_lwlock = lwlocks;
for (int i = 0; i < num_candidates; i++) {
tmplock.be_idx = candidates_pos[i];
tmplock.held_lwlocks = (lwlock_id_mode*)palloc0(get_held_lwlocks_maxnum() * sizeof(lwlock_id_mode));
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + candidates_pos[i];
lwm_light_detect* cur_det = light_det + candidates_pos[i];
bool success = true;
START_CRIT_SECTION();
beentry->st_lw_access_flag = true;
pg_read_barrier();
if (beentry->st_lw_is_cleanning_flag) {
beentry->st_lw_access_flag = false;
END_CRIT_SECTION();
continue;
}
for (;;) {
int before_changecount;
int after_changecount;
pgstat_save_changecount_before(beentry, before_changecount);
if (cur_det->entry_id.thread_id == beentry->st_procpid) {
tmplock.be_tid.thread_id = beentry->st_procpid;
tmplock.be_tid.st_sessionid = beentry->st_sessionid;
tmplock.want_lwlock.lock = beentry->lw_want_lock;
if (cur_det->lw_count == beentry->lw_count) {
tmplock.lwlocks_num = *beentry->lw_held_num;
copy_held_lwlocks(beentry->lw_held_locks, tmplock.held_lwlocks, tmplock.lwlocks_num);
} else {
success = false;
break;
}
} else {
success = false;
break;
}
pgstat_save_changecount_after(beentry, after_changecount);
if ((cur_det->lw_count == beentry->lw_count) && (before_changecount == after_changecount) &&
(before_changecount & 1) == 0) {
break;
}
}
pg_memory_barrier();
beentry->st_lw_access_flag = false;
END_CRIT_SECTION();
if (success) {
cur_lwlock->be_idx = tmplock.be_idx;
cur_lwlock->be_tid.thread_id = tmplock.be_tid.thread_id;
cur_lwlock->be_tid.st_sessionid = tmplock.be_tid.st_sessionid;
cur_lwlock->want_lwlock = tmplock.want_lwlock;
cur_lwlock->lwlocks_num = tmplock.lwlocks_num;
cur_lwlock->held_lwlocks = tmplock.held_lwlocks;
} else {
pfree_ext(tmplock.held_lwlocks);
}
++cur_lwlock;
}
return lwlocks;
}
TimestampTz pgstat_read_xact_start_tm(int be_index)
{
volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + be_index;
TimestampTz tmp_xact_tm = 0;
for (;;) {
int before_changecount;
int after_changecount;
pgstat_save_changecount_before(beentry, before_changecount);
if (beentry->st_procpid > 0) {
tmp_xact_tm = beentry->st_xact_start_timestamp;
}
pgstat_save_changecount_after(beentry, after_changecount);
if (before_changecount == after_changecount && (before_changecount & 1) == 0) {
break;
}
}
return tmp_xact_tm;
}
* @Description: parellel get table's distribution through ExecRemoteFunctionInParallel in RemoteFunctionResultHandler.
* @return : distribution info contain function state and tuple slot.
* NOTICE : the memory palloced in this function should be free outside where it was called.
*/
TableDistributionInfo* getTableDataDistribution(TupleDesc tuple_desc, char* schema_name, char* table_name)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
if (schema_name != NULL && table_name != NULL) {
appendStringInfo(&buf,
"select "
"n.nspname, "
"c.relname, "
"pg_catalog.pgxc_node_str(), "
"pg_catalog.pg_table_size(c.oid) "
"from pg_catalog.pg_class c "
"inner join pg_catalog.pg_namespace n on c.relnamespace = n.oid "
"where n.nspname = \'%s\' and c.relname=\'%s\' ",
schema_name,
table_name);
} else {
* table. */
appendStringInfo(&buf,
"select "
"n.nspname, "
"c.relname, "
"pg_catalog.pgxc_node_str(), "
"pg_catalog.pg_table_size(c.oid) "
"from pg_class c "
"inner join pg_catalog.pg_namespace n on c.relnamespace = n.oid "
"where c.relkind = 'r' and c.oid > %d and n.nspname <> 'cstore' and n.nspname <> 'pmk';",
FirstNormalObjectId);
}
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_DATANODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}
TableDistributionInfo* getTableStat(TupleDesc tuple_desc, int dirty_pecent, int n_tuples, char* schema_name)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
if (schema_name == NULL)
appendStringInfo(&buf,
"select "
"c.relname, "
"n.nspname, "
"pg_catalog.pg_stat_get_tuples_inserted(c.oid), "
"pg_catalog.pg_stat_get_tuples_updated(c.oid), "
"pg_catalog.pg_stat_get_tuples_deleted(c.oid), "
"pg_catalog.pg_stat_get_live_tuples(c.oid) n_live_tuples, "
"pg_catalog.pg_stat_get_dead_tuples(c.oid) n_dead_tuples "
"from pg_class c "
"left join pg_namespace n on (c.relnamespace = n.oid) "
"where c.relkind = 'r' and relpersistence <> 't' "
"and cast((n_dead_tuples)/(n_live_tuples+n_dead_tuples+0.00001) * 100 "
"as pg_catalog.numeric(5,2)) >= %d "
"and (n_live_tuples+n_dead_tuples) >= %d "
"and n.nspname not in ('pg_toast','information_schema','cstore','pmk'); ",
dirty_pecent,
n_tuples);
else
appendStringInfo(&buf,
"select "
"c.relname, "
"n.nspname, "
"pg_catalog.pg_stat_get_tuples_inserted(c.oid), "
"pg_catalog.pg_stat_get_tuples_updated(c.oid), "
"pg_catalog.pg_stat_get_tuples_deleted(c.oid), "
"pg_catalog.pg_stat_get_live_tuples(c.oid) n_live_tuples, "
"pg_catalog.pg_stat_get_dead_tuples(c.oid) n_dead_tuples "
"from pg_class c "
"left join pg_namespace n on (c.relnamespace = n.oid) "
"where c.relkind = 'r' and relpersistence <> 't' "
"and cast((n_dead_tuples)/(n_live_tuples+n_dead_tuples+0.00001) * 100 "
"as pg_catalog.numeric(5,2)) >= %d "
"and (n_live_tuples+n_dead_tuples) >= %d "
"and n.nspname = \'%s\'; ",
dirty_pecent,
n_tuples,
schema_name);
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_DATANODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}
TableDistributionInfo* get_remote_stat_pagewriter(TupleDesc tuple_desc)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
appendStringInfo(&buf,
"select "
"node_name, pgwr_actual_flush_total_num, pgwr_last_flush_num, remain_dirty_page_num, "
"queue_head_page_rec_lsn, queue_rec_lsn, current_xlog_insert_lsn, ckpt_redo_point "
"from pg_catalog.local_pagewriter_stat(); ");
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}
TableDistributionInfo* get_remote_stat_ckpt(TupleDesc tuple_desc)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
appendStringInfo(&buf,
"select "
"node_name,ckpt_redo_point,ckpt_clog_flush_num,ckpt_csnlog_flush_num, "
"ckpt_multixact_flush_num,ckpt_predicate_flush_num,ckpt_twophase_flush_num "
"from pg_catalog.local_ckpt_stat(); ");
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}
TableDistributionInfo* get_remote_stat_bgwriter(TupleDesc tuple_desc)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
appendStringInfo(&buf,
"select "
"node_name,bgwr_actual_flush_total_num,bgwr_last_flush_num,candidate_slots, "
"get_buffer_from_list,get_buf_clock_sweep "
"from pg_catalog.local_bgwriter_stat(); ");
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}
TableDistributionInfo* get_remote_stat_candidate(TupleDesc tuple_desc)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
appendStringInfo(&buf,
"select "
"node_name,candidate_slots,get_buf_from_list,get_buf_clock_sweep, "
"seg_candidate_slots,seg_get_buf_from_list,seg_get_buf_clock_sweep "
"from pg_catalog.local_candidate_stat(); ");
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}
TableDistributionInfo* get_remote_single_flush_dw_stat(TupleDesc tuple_desc)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
appendStringInfo(&buf,
"SELECT node_name, curr_dwn, curr_start_page, total_writes, file_trunc_num, file_reset_num "
"FROM pg_catalog.local_single_flush_dw_stat();");
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}
TableDistributionInfo* get_remote_stat_double_write(TupleDesc tuple_desc)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
appendStringInfo(&buf,
"SELECT node_name, curr_dwn, curr_start_page, file_trunc_num, file_reset_num, "
"total_writes, low_threshold_writes, high_threshold_writes, "
"total_pages, low_threshold_pages, high_threshold_pages, file_id "
"FROM pg_catalog.local_double_write_stat();");
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}
TableDistributionInfo* get_remote_stat_redo(TupleDesc tuple_desc)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
appendStringInfo(&buf,
"SELECT node_name, redo_start_ptr, redo_start_time, redo_done_time, curr_time, "
"min_recovery_point, read_ptr, last_replayed_read_ptr, recovery_done_ptr, "
"read_xlog_io_counter, read_xlog_io_total_dur, "
"read_data_io_counter, read_data_io_total_dur, "
"write_data_io_counter, write_data_io_total_dur, "
"process_pending_counter, process_pending_total_dur, "
"apply_counter, apply_total_dur, "
"speed, local_max_ptr, primary_flush_ptr, worker_info "
"FROM pg_catalog.local_redo_stat();");
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}
TableDistributionInfo* get_rto_stat(TupleDesc tuple_desc)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
appendStringInfo(&buf, "SELECT node_name, rto_info FROM pg_catalog.local_rto_stat();");
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}
TableDistributionInfo* get_recovery_stat(TupleDesc tuple_desc)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
appendStringInfo(&buf,
"SELECT node_name, standby_node_name, source_ip, source_port, dest_ip, dest_port, current_rto, target_rto, "
"current_sleep_time FROM "
"pg_catalog.local_recovery_status();");
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}
TableDistributionInfo* streaming_hadr_get_recovery_stat(TupleDesc tuple_desc)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
appendStringInfo(&buf,
"SELECT hadr_sender_node_name, hadr_receiver_node_name, "
"source_ip, source_port, dest_ip, dest_port, current_rto, target_rto, current_rpo, target_rpo, "
"rto_sleep_time, rpo_sleep_time FROM "
"pg_catalog.gs_hadr_local_rto_and_rpo_stat();");
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}
TableDistributionInfo* get_remote_node_xid_csn(TupleDesc tuple_desc)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
appendStringInfo(&buf, "select node_name, next_xid, next_csn FROM pg_catalog.gs_get_next_xid_csn();");
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}
#ifdef ENABLE_MULTIPLE_NODES
TableDistributionInfo* get_remote_index_status(TupleDesc tuple_desc, const char *schname, const char *idxname)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
appendStringInfo(&buf, "select a.node_name::text, b.indisready, b.indisvalid from pg_index b "
"left join pgxc_node a on b.xc_node_id = a.node_id "
"where indexrelid = (select oid from pg_class where relnamespace = "
"(select oid from pg_namespace where nspname = %s) "
"and relname = %s);", quote_literal_cstr(schname), quote_literal_cstr(idxname));
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
pfree_ext(buf.data);
return distribuion_info;
}
#endif
* the whole process statistics of bad block
* used for query statistics of bad block
* update by pgstat_recv_badblock_stat()
*/
MemoryContext global_bad_block_mcxt = NULL;
HTAB* global_bad_block_stat = NULL;
* Record a statistics of bad block:
* Backend thread: read a bad page/cu --> addBadBlockStat() record in local_bad_block_stat -->
* pgstat_send_badblock_stat() send msg to PgstatCollector thread PgstatCollector thread: pgstat_recv_badblock_stat
* receive msg from Backen thread, and record in global_bad_block_stat
*
* Query statistics of bad block:
* Backend thread: pg_stat_bad_block() read the global_bad_block_stat and output
*/
* @Description: init process statistics of bad block hash table
* @See also:
*/
void initGlobalBadBlockStat()
{
if (global_bad_block_mcxt == NULL) {
global_bad_block_mcxt = AllocSetContextCreate((MemoryContext)g_instance.instance_context,
"bad block stat global memory context",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE,
SHARED_CONTEXT);
}
LWLockAcquire(BadBlockStatHashLock, LW_EXCLUSIVE);
if (global_bad_block_stat == NULL) {
HASHCTL hash_ctl;
errno_t errval = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl));
securec_check_errval(errval, , LOG);
hash_ctl.hcxt = global_bad_block_mcxt;
hash_ctl.keysize = sizeof(BadBlockHashKey);
hash_ctl.entrysize = sizeof(BadBlockHashEnt);
hash_ctl.hash = BadBlockHashKeyHash;
hash_ctl.match = BadBlockHashKeyMatch;
global_bad_block_stat = hash_create("bad block stat global hash table", 64, &hash_ctl,
HASH_ELEM | HASH_SHRCTX | HASH_FUNCTION | HASH_COMPARE);
}
LWLockRelease(BadBlockStatHashLock);
}
* @Description: init thread statistics of bad block hash table
* @See also:
*/
void initLocalBadBlockStat()
{
if (t_thrd.stat_cxt.local_bad_block_mcxt == NULL) {
t_thrd.stat_cxt.local_bad_block_mcxt = AllocSetContextCreate((MemoryContext)t_thrd.top_mem_cxt,
"bad block session memory context",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
}
if (t_thrd.stat_cxt.local_bad_block_stat == NULL) {
HASHCTL hash_ctl;
errno_t errval = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl));
securec_check_errval(errval, , LOG);
hash_ctl.hcxt = t_thrd.stat_cxt.local_bad_block_mcxt;
hash_ctl.keysize = sizeof(BadBlockHashKey);
hash_ctl.entrysize = sizeof(BadBlockHashEnt);
hash_ctl.hash = BadBlockHashKeyHash;
hash_ctl.match = BadBlockHashKeyMatch;
t_thrd.stat_cxt.local_bad_block_stat = hash_create("bad block stat session hash table", 16, &hash_ctl,
HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT | HASH_COMPARE);
}
}
* @Description: add statistics of bad block where read a bad page/cu
* @IN relfilenode: relfilenode for page/cu
* @IN forknum: forknum for page/cu
* @See also:
*/
void addBadBlockStat(const RelFileNode* relfilenode, ForkNumber forknum)
{
if (t_thrd.stat_cxt.local_bad_block_stat == NULL || relfilenode == NULL) {
return;
}
TimestampTz last_time = GetCurrentTimestamp();
BadBlockHashKey hash_key;
hash_key.relfilenode.spcNode = relfilenode->spcNode;
hash_key.relfilenode.dbNode = relfilenode->dbNode;
hash_key.relfilenode.relNode = relfilenode->relNode;
hash_key.relfilenode.bucketNode = relfilenode->bucketNode;
hash_key.relfilenode.opt = relfilenode->opt;
hash_key.forknum = forknum;
bool found = false;
BadBlockHashEnt* entry =
(BadBlockHashEnt*)hash_search(t_thrd.stat_cxt.local_bad_block_stat, &hash_key, HASH_ENTER, &found);
if (entry != NULL) {
if (!found) {
entry->first_time = last_time;
entry->error_count = 0;
}
++entry->error_count;
entry->last_time = last_time;
}
}
* @Description: clear the whole process statistics of bad block
* @See also:
*/
void resetBadBlockStat()
{
if (global_bad_block_stat == NULL)
return;
LWLockAcquire(BadBlockStatHashLock, LW_EXCLUSIVE);
if (global_bad_block_stat == NULL) {
LWLockRelease(BadBlockStatHashLock);
return;
}
hash_remove(global_bad_block_stat);
global_bad_block_stat = NULL;
HASHCTL hash_ctl;
errno_t errval = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl));
securec_check_errval(errval, , LOG);
hash_ctl.hcxt = global_bad_block_mcxt;
hash_ctl.keysize = sizeof(BadBlockHashKey);
hash_ctl.entrysize = sizeof(BadBlockHashEnt);
hash_ctl.hash = BadBlockHashKeyHash;
hash_ctl.match = BadBlockHashKeyMatch;
global_bad_block_stat =
hash_create("bad block stat hash table", 64, &hash_ctl, HASH_ELEM | HASH_SHRCTX | HASH_FUNCTION | HASH_COMPARE);
LWLockRelease(BadBlockStatHashLock);
}
* @Description: Subroutine for pgstat_report_stat: populate and send a bad block stat message
* @See also:
*/
static void pgstat_send_badblock_stat(void)
{
if (t_thrd.stat_cxt.local_bad_block_stat == NULL)
return;
PgStat_MsgBadBlock msg;
HASH_SEQ_STATUS hash_seq;
BadBlockHashEnt* badblock_entry = NULL;
pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_BADBLOCK);
msg.m_nentries = 0;
hash_seq_init(&hash_seq, t_thrd.stat_cxt.local_bad_block_stat);
while ((badblock_entry = (BadBlockHashEnt*)hash_seq_search(&hash_seq)) != NULL) {
if (badblock_entry->error_count == 0)
continue;
BadBlockHashEnt* m_ent = &msg.m_entry[msg.m_nentries];
errno_t rc = memcpy_s(m_ent, sizeof(BadBlockHashEnt), badblock_entry, sizeof(BadBlockHashEnt));
securec_check(rc, "", "");
if ((unsigned int)++msg.m_nentries >= PGSTAT_NUM_BADBLOCK_ENTRIES) {
pgstat_send(&msg, offsetof(PgStat_MsgBadBlock, m_entry[0]) + msg.m_nentries * sizeof(BadBlockHashEnt));
msg.m_nentries = 0;
}
badblock_entry->error_count = 0;
badblock_entry->first_time = 0;
badblock_entry->last_time = 0;
}
if (msg.m_nentries > 0)
pgstat_send(&msg, offsetof(PgStat_MsgBadBlock, m_entry[0]) + msg.m_nentries * sizeof(BadBlockHashEnt));
}
* @Description: Process bad block stat message
* @IN msg: bad block stat message
* @IN len: without use
* @See also:
*/
static void pgstat_recv_badblock_stat(PgStat_MsgBadBlock* msg, int )
{
if (global_bad_block_stat == NULL || msg == NULL) {
return;
}
BadBlockHashEnt* badblock_entry = &(msg->m_entry[0]);
LWLockAcquire(BadBlockStatHashLock, LW_EXCLUSIVE);
if (global_bad_block_stat == NULL) {
LWLockRelease(BadBlockStatHashLock);
return;
}
for (int i = 0; i < msg->m_nentries; i++, badblock_entry++) {
bool found = false;
BadBlockHashKey* badblock_key = &(badblock_entry->key);
BadBlockHashEnt* entry = (BadBlockHashEnt*)hash_search(global_bad_block_stat, badblock_key, HASH_ENTER, &found);
if (entry != NULL) {
if (!found) {
entry->first_time = badblock_entry->first_time;
entry->error_count = 0;
}
entry->error_count += badblock_entry->error_count;
entry->last_time = badblock_entry->last_time;
}
}
LWLockRelease(BadBlockStatHashLock);
}
* get current total counter
*/
void GetCurrentTotalTableCounter(PgStat_TableCounts* total_table_counter)
{
TabStatusArray* tsa = NULL;
for (tsa = u_sess->stat_cxt.pgStatTabList; tsa != NULL; tsa = tsa->tsa_next) {
for (int i = 0; i < tsa->tsa_used; i++) {
PgStat_TableStatus* entry = &tsa->tsa_entries[i];
PgStat_TableXactStatus* trans = NULL;
if (entry != NULL) {
UniqueSQLSumTableStatCounter((*total_table_counter), entry->t_counts);
for (trans = entry->trans; trans != NULL; trans = trans->upper) {
total_table_counter->t_tuples_inserted += trans->tuples_inserted;
total_table_counter->t_tuples_updated += trans->tuples_updated;
total_table_counter->t_tuples_deleted += trans->tuples_deleted;
}
}
}
}
}
* CalcSQLRowStatCounter - get current unique sql's row
* activity's counter
*
* Used for Instrumentation/UniqueSQL.
*
* last_total_counter - when exit pgstat_report_stat last time,
* store the total counter into last_total_counter.
* current_table_counter- current unique sql's stat counter
*
* return true if current_table_counter has values
*/
bool CalcSQLRowStatCounter(PgStat_TableCounts* last_total_counter, PgStat_TableCounts* current_sql_table_counter)
{
Assert(current_sql_table_counter && last_total_counter);
if (u_sess->stat_cxt.pgStatTabList == NULL || u_sess->stat_cxt.pgStatTabList->tsa_used == 0)
return false;
* dont calculate Counter for towphase's Prepare since tuple activity has been removed from
* PgStat_TableCounts in PostPrepare_PgStat. if do it, smaller number will be found in T
* than L's, and Prepare will has minus number for row stats.
*/
if (u_sess->storage_cxt.twoPhaseCommitInProgress)
return false;
ereport(DEBUG1,
(errmodule(MOD_INSTR),
errmsg("[UniqueSQL] unique id: %lu, calc row stat counter", u_sess->unique_sql_cxt.unique_sql_id)));
PrintPgStatTableCounter('L', last_total_counter);
PgStat_TableCounts total_table_counter = {0};
GetCurrentTotalTableCounter(&total_table_counter);
PrintPgStatTableCounter('T', &total_table_counter);
errno_t rc;
if (total_table_counter.t_tuples_returned != 0 || total_table_counter.t_tuples_fetched != 0 ||
total_table_counter.t_tuples_inserted != 0 || total_table_counter.t_tuples_updated != 0 ||
total_table_counter.t_tuples_deleted != 0 || total_table_counter.t_blocks_fetched != 0 ||
total_table_counter.t_blocks_hit != 0) {
UniqueSQLDiffTableStatCounter((*current_sql_table_counter), total_table_counter, (*last_total_counter));
PrintPgStatTableCounter('C', current_sql_table_counter);
* if local counter is cleaned in pgstat_report_stat method,
* reset last total counter in pgstat_report_stat method.
*
* if not cleaned, update last total counter to total counter.
*/
rc = memcpy_s(last_total_counter, sizeof(PgStat_TableCounts), &total_table_counter, sizeof(PgStat_TableCounts));
securec_check(rc, "\0", "\0");
return true;
} else {
rc = memset_s(last_total_counter, sizeof(PgStat_TableCounts), 0, sizeof(PgStat_TableCounts));
securec_check(rc, "\0", "\0");
return false;
}
}
int pgstat_fetch_sql_rt_info_counter(void)
{
if (g_instance.stat_cxt.sql_rt_info_array != NULL) {
if (IS_SINGLE_NODE) {
prepare_calculate_single(g_instance.stat_cxt.sql_rt_info_array, &u_sess->percentile_cxt.LocalCounter);
} else {
prepare_calculate(g_instance.stat_cxt.sql_rt_info_array, &u_sess->percentile_cxt.LocalCounter);
}
}
return u_sess->percentile_cxt.LocalCounter;
}
void pgstat_fetch_sql_rt_info_internal(SqlRTInfo* sqlrt)
{
if (sqlrt == NULL || u_sess->percentile_cxt.LocalCounter == 0 || u_sess->percentile_cxt.LocalsqlRT == NULL)
return;
errno_t rc = memcpy_s(sqlrt,
u_sess->percentile_cxt.LocalCounter * sizeof(SqlRTInfo),
u_sess->percentile_cxt.LocalsqlRT,
u_sess->percentile_cxt.LocalCounter * sizeof(SqlRTInfo));
securec_check(rc, "\0", "\0");
pfree_ext(u_sess->percentile_cxt.LocalsqlRT);
}
void pgstat_reply_percentile_record_count()
{
if (u_sess->percentile_cxt.LocalsqlRT != NULL) {
pfree_ext(u_sess->percentile_cxt.LocalsqlRT);
u_sess->percentile_cxt.LocalCounter = 0;
}
(void)pgstat_fetch_sql_rt_info_counter();
StringInfoData buf;
pq_beginmessage(&buf, 'c');
pq_sendint(&buf, u_sess->percentile_cxt.LocalCounter, sizeof(int));
pq_endmessage(&buf);
pq_beginmessage(&buf, 'f');
pq_endmessage(&buf);
pq_flush();
}
void pgstat_reply_percentile_record()
{
StringInfoData buf;
if (u_sess->percentile_cxt.LocalsqlRT != NULL) {
for (int i = 0; i < u_sess->percentile_cxt.LocalCounter; i++) {
pq_beginmessage(&buf, 'r');
pq_sendint64(&buf, u_sess->percentile_cxt.LocalsqlRT[i].UniqueSQLId);
pq_sendint64(&buf, u_sess->percentile_cxt.LocalsqlRT[i].rt);
pq_endmessage(&buf);
}
pfree_ext(u_sess->percentile_cxt.LocalsqlRT);
}
pq_beginmessage(&buf, 'f');
pq_endmessage(&buf);
pq_flush();
}
void pgstat_release_session_memory_entry()
{
if (t_thrd.shemem_ptr_cxt.mySessionMemoryEntry != NULL) {
pfree_ext(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->query_plan);
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->plan_size = 0;
pfree_ext(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->query_plan_issue);
}
}
TableDistributionInfo* GetRemoteGsLWLockStatus(TupleDesc tuple_desc)
{
StringInfoData buf;
TableDistributionInfo* distribuion_info = NULL;
distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo));
initStringInfo(&buf);
appendStringInfo(&buf, "select * FROM pg_catalog.gs_lwlock_status();");
distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true);
distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc);
return distribuion_info;
}