pax_global_header00006660000000000000000000000064140042372560014515gustar00rootroot0000000000000052 comment=b984f33528a479243884b8e23bac1240d4566bcc pg_wait_sampling-1.1.3/000077500000000000000000000000001400423725600150435ustar00rootroot00000000000000pg_wait_sampling-1.1.3/.gitignore000066400000000000000000000001031400423725600170250ustar00rootroot00000000000000.deps *.o *.so /results *pg_wait_sampling--1.1.sql .log Dockerfile pg_wait_sampling-1.1.3/.travis.yml000066400000000000000000000013441400423725600171560ustar00rootroot00000000000000os: - linux sudo: required dist: trusty language: c services: - docker install: - sed -e 's/${CHECK_CODE}/'${CHECK_CODE}/g -e 's/${PG_VERSION}/'${PG_VERSION}/g Dockerfile.tmpl > Dockerfile - docker-compose build script: - docker-compose run tests env: - PG_VERSION=9.6 CHECK_CODE=clang - PG_VERSION=9.6 CHECK_CODE=cppcheck - PG_VERSION=9.6 CHECK_CODE=false - PG_VERSION=10 CHECK_CODE=clang - PG_VERSION=10 CHECK_CODE=cppcheck - PG_VERSION=10 CHECK_CODE=false - PG_VERSION=11 CHECK_CODE=clang - PG_VERSION=11 CHECK_CODE=false - PG_VERSION=12 CHECK_CODE=clang - PG_VERSION=12 CHECK_CODE=false - PG_VERSION=13 CHECK_CODE=clang - PG_VERSION=13 CHECK_CODE=false pg_wait_sampling-1.1.3/Dockerfile.tmpl000066400000000000000000000025621400423725600200150ustar00rootroot00000000000000FROM postgres:${PG_VERSION}-alpine ENV LANG=C.UTF-8 PGDATA=/pg/data RUN if [ "${CHECK_CODE}" = "clang" ] ; then \ # echo 'http://dl-3.alpinelinux.org/alpine/edge/main' > /etc/apk/repositories; \ # Use alpine/v3.6/main instead of alpine/edge/main to fix version of clang to '8.*.*' apk --no-cache add clang-analyzer make musl-dev gcc --repository http://dl-cdn.alpinelinux.org/alpine/v3.6/main; \ fi RUN if [ "${CHECK_CODE}" = "cppcheck" ] ; then \ apk --no-cache add cppcheck --repository http://dl-cdn.alpinelinux.org/alpine/v3.6/community; \ fi RUN if [ "${CHECK_CODE}" = "false" ] ; then \ # echo 'http://dl-3.alpinelinux.org/alpine/edge/main' > /etc/apk/repositories; \ # Use alpine/v3.6/main instead of alpine/edge/main to fix version of clang to '8.*.*' # Install clang as well, since LLVM is enabled in PG_VERSION >= 11 by default apk --no-cache add curl python3 gcc make musl-dev llvm clang clang-dev \ --repository http://dl-cdn.alpinelinux.org/alpine/v3.6/community \ --repository http://dl-cdn.alpinelinux.org/alpine/v3.6/main; \ fi RUN mkdir -p ${PGDATA} && \ mkdir /pg/src && \ chown postgres:postgres ${PGDATA} && \ chmod -R a+rwx /usr/local/lib/postgresql && \ chmod a+rwx /usr/local/share/postgresql/extension ADD . /pg/src WORKDIR /pg/src RUN chmod -R go+rwX /pg/src USER postgres ENTRYPOINT PGDATA=${PGDATA} CHECK_CODE=${CHECK_CODE} bash run_tests.sh pg_wait_sampling-1.1.3/LICENSE000066400000000000000000000023531400423725600160530ustar00rootroot00000000000000pg_wait_sampling is released under the PostgreSQL License, a liberal Open Source license, similar to the BSD or MIT licenses. Copyright (c) 2015-2017, Postgres Professional Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group Portions Copyright (c) 1994, The Regents of the University of California Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. IN NO EVENT SHALL POSTGRES PROFESSIONAL BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF POSTGRES PROFESSIONAL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. POSTGRES PROFESSIONAL SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND POSTGRES PROFESSIONAL HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. pg_wait_sampling-1.1.3/META.json000066400000000000000000000026001400423725600164620ustar00rootroot00000000000000{ "name": "pg_wait_sampling", "abstract": "Sampling based statistics of wait events", "description": "pg_wait_sampling provides functions for detailed per backend and per query statistics about PostgreSQL wait events", "version": "1.1.3", "maintainer": [ "Alexander Korotkov ", "Ildus Kurbangaliev " ], "license": { "PostgreSQL": "http://www.postgresql.org/about/licence" }, "prereqs": { "runtime": { "requires": { "PostgreSQL": "9.6.0" } } }, "provides": { "pg_wait_sampling": { "file": "pg_wait_sampling--1.1.sql", "docfile": "README.md", "version": "1.1.3", "abstract": "Sampling based statistics of wait events" } }, "resources": { "bugtracker": { "web": "https://github.com/postgrespro/pg_wait_sampling/issues" }, "repository": { "url": "https://github.com/postgrespro/pg_wait_sampling.git", "web": "https://github.com/postgrespro/pg_wait_sampling", "type": "git" } }, "generated_by": "Ildus Kurbangaliev", "meta-spec": { "version": "1.0.0", "url": "http://pgxn.org/meta/spec.txt" }, "tags": [ "waits", "sampling", "background worker", "wait events", "waits history", "waits profile" ] } pg_wait_sampling-1.1.3/Makefile000066400000000000000000000017441400423725600165110ustar00rootroot00000000000000# contrib/pg_wait_sampling/Makefile MODULE_big = pg_wait_sampling OBJS = pg_wait_sampling.o collector.o compat.o EXTENSION = pg_wait_sampling EXTVERSION = 1.1 DATA_built = pg_wait_sampling--$(EXTVERSION).sql DATA = pg_wait_sampling--1.0--1.1.sql REGRESS = load queries EXTRA_REGRESS_OPTS=--temp-config=$(top_srcdir)/$(subdir)/conf.add EXTRA_CLEAN = pg_wait_sampling--$(EXTVERSION).sql ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) else subdir = contrib/pg_wait_sampling top_builddir = ../.. include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif $(EXTENSION)--$(EXTVERSION).sql: setup.sql cat $^ > $@ # Prepare the package for PGXN submission DISTVERSION := $(shell git tag -l | tail -n 1 | cut -d 'v' -f 2) package: dist dist/$(EXTENSION)-$(DISTVERSION).zip dist: mkdir -p dist dist/$(EXTENSION)-$(DISTVERSION).zip: git archive --format zip --prefix=$(EXTENSION)-$(DISTVERSION)/ --output $@ HEAD pg_wait_sampling-1.1.3/README.md000066400000000000000000000177411400423725600163340ustar00rootroot00000000000000[![Build Status](https://travis-ci.com/postgrespro/pg_wait_sampling.svg?branch=master)](https://travis-ci.com/postgrespro/pg_wait_sampling) [![PGXN version](https://badge.fury.io/pg/pg_wait_sampling.svg)](https://badge.fury.io/pg/pg_wait_sampling) [![GitHub license](https://img.shields.io/badge/license-PostgreSQL-blue.svg)](https://raw.githubusercontent.com/postgrespro/pg_wait_sampling/master/LICENSE) `pg_wait_sampling` – sampling based statistics of wait events ============================================================= Introduction ------------ PostgreSQL 9.6+ provides an information about current wait event of particular process. However, in order to gather descriptive statistics of server behavior user have to sample current wait event multiple times. `pg_wait_sampling` is an extension for collecting sampling statistics of wait events. The module must be loaded by adding `pg_wait_sampling` to `shared_preload_libraries` in postgresql.conf, because it requires additional shared memory and launches background worker. This means that a server restart is needed to add or remove the module. When `pg_wait_sampling` is enabled, it collects two kinds of statistics. * History of waits events. It's implemented as in-memory ring buffer where samples of each process wait events are written with given (configurable) period. Therefore, for each running process user can see some number of recent samples depending on history size (configurable). Assuming there is a client who periodically read this history and dump it somewhere, user can have continuous history. * Waits profile. It's implemented as in-memory hash table where count of samples are accumulated per each process and each wait event (and each query with `pg_stat_statements`). This hash table can be reset by user request. Assuming there is a client who periodically dumps profile and resets it, user can have statistics of intensivity of wait events among time. In combination with `pg_stat_statements` this extension can also provide per query statistics. `pg_wait_sampling` launches special background worker for gathering the statistics above. Availability ------------ `pg_wait_sampling` is implemented as an extension and not available in default PostgreSQL installation. It is available from [github](https://github.com/postgrespro/pg_wait_sampling) under the same license as [PostgreSQL](http://www.postgresql.org/about/licence/) and supports PostgreSQL 9.6+. Installation ------------ `pg_wait_sampling` is PostgreSQL extension which requires PostgreSQL 9.6 or higher. Before build and install you should ensure following: * PostgreSQL version is 9.6 or higher. * You have development package of PostgreSQL installed or you built PostgreSQL from source. * Your PATH variable is configured so that `pg_config` command available, or set PG_CONFIG variable. Typical installation procedure may look like this: $ git clone https://github.com/postgrespro/pg_wait_sampling.git $ cd pg_wait_sampling $ make USE_PGXS=1 $ sudo make USE_PGXS=1 install $ make USE_PGXS=1 installcheck $ psql DB -c "CREATE EXTENSION pg_wait_sampling;" Compilation on Windows is not supported, since the extension uses symbols from PostgreSQL that are not exported. Usage ----- `pg_wait_sampling` interacts with user by set of views and functions. `pg_wait_sampling_current` view – information about current wait events for all processed including background workers. | Column name | Column type | Description | | ----------- | ----------- | ----------------------- | | pid | int4 | Id of process | | event_type | text | Name of wait event type | | event | text | Name of wait event | | queryid | int8 | Id of query | `pg_wait_sampling_get_current(pid int4)` returns the same table for single given process. `pg_wait_sampling_history` view – history of wait events obtained by sampling into in-memory ring buffer. | Column name | Column type | Description | | ----------- | ----------- | ----------------------- | | pid | int4 | Id of process | | ts | timestamptz | Sample timestamp | | event_type | text | Name of wait event type | | event | text | Name of wait event | | queryid | int8 | Id of query | `pg_wait_sampling_profile` view – profile of wait events obtained by sampling into in-memory hash table. | Column name | Column type | Description | | ----------- | ----------- | ----------------------- | | pid | int4 | Id of process | | event_type | text | Name of wait event type | | event | text | Name of wait event | | queryid | int8 | Id of query | | count | text | Count of samples | `pg_wait_sampling_reset_profile()` function resets the profile. The work of wait event statistics collector worker is controlled by following GUCs. | Parameter name | Data type | Description | Default value | | ----------------------------------- | --------- | ------------------------------------------- | ------------: | | pg_wait_sampling.history_size | int4 | Size of history in-memory ring buffer | 5000 | | pg_wait_sampling.history_period | int4 | Period for history sampling in milliseconds | 10 | | pg_wait_sampling.profile_period | int4 | Period for profile sampling in milliseconds | 10 | | pg_wait_sampling.profile_pid | bool | Whether profile should be per pid | true | | pg_wait_sampling.profile_queries | bool | Whether profile should be per query | false | If `pg_wait_sampling.profile_pid` is set to false, sampling profile wouldn't be collected in per-process manner. In this case the value of pid could would be always zero and corresponding row contain samples among all the processes. While `pg_wait_sampling.profile_queries` is set to false `queryid` field in views will be zero. These GUCs are allowed to be changed by superuser. Also, they are placed into shared memory. Thus, they could be changed from any backend and affects worker runtime. See [PostgreSQL documentation](http://www.postgresql.org/docs/devel/static/monitoring-stats.html#WAIT-EVENT-TABLE) for list of possible wait events. Contribution ------------ Please, notice, that `pg_wait_sampling` is still under development and while it's stable and tested, it may contains some bugs. Don't hesitate to raise [issues at github](https://github.com/postgrespro/pg_wait_sampling/issues) with your bug reports. If you're lacking of some functionality in `pg_wait_sampling` and feeling power to implement it then you're welcome to make pull requests. Releases -------- New features are developed in feature-branches and then merged into [master](https://github.com/postgrespro/pg_wait_sampling/tree/master). To make a new release: 1) Bump `PGXN` version in the `META.json`. 2) Merge [master](https://github.com/postgrespro/pg_wait_sampling/tree/master) into [stable](https://github.com/postgrespro/pg_wait_sampling/tree/stable). 3) Tag new release in the [stable](https://github.com/postgrespro/pg_wait_sampling/tree/stable) with `git tag -a v1.1.X`, where the last digit is used for indicating compatible shared library changes and bugfixes. Second digit is used to indicate extension schema change, i.e. when `ALTER EXTENSION pg_wait_sampling UPDATE;` is required. 4) Merge [stable](https://github.com/postgrespro/pg_wait_sampling/tree/stable) into [debian](https://github.com/postgrespro/pg_wait_sampling/tree/debian). This separate branch is used to independently support `Debian` packaging and @anayrat with @df7cb have an access there. Authors ------- * Alexander Korotkov , Postgres Professional, Moscow, Russia * Ildus Kurbangaliev , Postgres Professional, Moscow, Russia pg_wait_sampling-1.1.3/collector.c000066400000000000000000000277111400423725600172050ustar00rootroot00000000000000/* * collector.c * Collector of wait event history and profile. * * Copyright (c) 2015-2016, Postgres Professional * * IDENTIFICATION * contrib/pg_wait_sampling/pg_wait_sampling.c */ #include "postgres.h" #include "catalog/pg_type.h" #if PG_VERSION_NUM >= 130000 #include "common/hashfn.h" #endif #include "funcapi.h" #include "miscadmin.h" #include "postmaster/bgworker.h" #include "storage/ipc.h" #include "storage/procarray.h" #include "storage/procsignal.h" #include "storage/shm_mq.h" #include "storage/shm_toc.h" #include "storage/spin.h" #include "utils/memutils.h" #include "utils/resowner.h" #include "pgstat.h" #include "pg_wait_sampling.h" static volatile sig_atomic_t shutdown_requested = false; static void handle_sigterm(SIGNAL_ARGS); /* * Register background worker for collecting waits history. */ void register_wait_collector(void) { BackgroundWorker worker; /* Set up background worker parameters */ memset(&worker, 0, sizeof(worker)); worker.bgw_flags = BGWORKER_SHMEM_ACCESS; worker.bgw_start_time = BgWorkerStart_ConsistentState; worker.bgw_restart_time = 0; worker.bgw_notify_pid = 0; snprintf(worker.bgw_library_name, BGW_MAXLEN, "pg_wait_sampling"); snprintf(worker.bgw_function_name, BGW_MAXLEN, CppAsString(collector_main)); snprintf(worker.bgw_name, BGW_MAXLEN, "pg_wait_sampling collector"); worker.bgw_main_arg = (Datum) 0; RegisterBackgroundWorker(&worker); } /* * Allocate memory for waits history. */ void alloc_history(History *observations, int count) { observations->items = (HistoryItem *) palloc0(sizeof(HistoryItem) * count); observations->index = 0; observations->count = count; observations->wraparound = false; } /* * Reallocate memory for changed number of history items. */ static void realloc_history(History *observations, int count) { HistoryItem *newitems; int copyCount, i, j; /* Allocate new array for history */ newitems = (HistoryItem *) palloc0(sizeof(HistoryItem) * count); /* Copy entries from old array to the new */ if (observations->wraparound) copyCount = observations->count; else copyCount = observations->index; copyCount = Min(copyCount, count); i = 0; if (observations->wraparound) j = observations->index + 1; else j = 0; while (i < copyCount) { if (j >= observations->count) j = 0; memcpy(&newitems[i], &observations->items[j], sizeof(HistoryItem)); i++; j++; } /* Switch to new history array */ pfree(observations->items); observations->items = newitems; observations->index = copyCount; observations->count = count; observations->wraparound = false; } static void handle_sigterm(SIGNAL_ARGS) { int save_errno = errno; shutdown_requested = true; if (MyProc) SetLatch(&MyProc->procLatch); errno = save_errno; } /* * Get next item of history with rotation. */ static HistoryItem * get_next_observation(History *observations) { HistoryItem *result; if (observations->index >= observations->count) { observations->index = 0; observations->wraparound = true; } result = &observations->items[observations->index]; observations->index++; return result; } /* * Read current waits from backends and write them to history array * and/or profile hash. */ static void probe_waits(History *observations, HTAB *profile_hash, bool write_history, bool write_profile, bool profile_pid) { int i, newSize; TimestampTz ts = GetCurrentTimestamp(); /* Realloc waits history if needed */ newSize = collector_hdr->historySize; if (observations->count != newSize) realloc_history(observations, newSize); /* Iterate PGPROCs under shared lock */ LWLockAcquire(ProcArrayLock, LW_SHARED); for (i = 0; i < ProcGlobal->allProcCount; i++) { HistoryItem item, *observation; PGPROC *proc = &ProcGlobal->allProcs[i]; if (proc->pid == 0) continue; if (proc->wait_event_info == 0) continue; /* Collect next wait event sample */ item.pid = proc->pid; item.wait_event_info = proc->wait_event_info; if (collector_hdr->profileQueries) item.queryId = proc_queryids[i]; else item.queryId = 0; item.ts = ts; /* Write to the history if needed */ if (write_history) { observation = get_next_observation(observations); *observation = item; } /* Write to the profile if needed */ if (write_profile) { ProfileItem *profileItem; bool found; if (!profile_pid) item.pid = 0; profileItem = (ProfileItem *) hash_search(profile_hash, &item, HASH_ENTER, &found); if (found) profileItem->count++; else profileItem->count = 1; } } LWLockRelease(ProcArrayLock); } /* * Send waits history to shared memory queue. */ static void send_history(History *observations, shm_mq_handle *mqh) { Size count, i; shm_mq_result mq_result; if (observations->wraparound) count = observations->count; else count = observations->index; mq_result = shm_mq_send(mqh, sizeof(count), &count, false); if (mq_result == SHM_MQ_DETACHED) { ereport(WARNING, (errmsg("pg_wait_sampling collector: " "receiver of message queue has been detached"))); return; } for (i = 0; i < count; i++) { mq_result = shm_mq_send(mqh, sizeof(HistoryItem), &observations->items[i], false); if (mq_result == SHM_MQ_DETACHED) { ereport(WARNING, (errmsg("pg_wait_sampling collector: " "receiver of message queue has been detached"))); return; } } } /* * Send profile to shared memory queue. */ static void send_profile(HTAB *profile_hash, shm_mq_handle *mqh) { HASH_SEQ_STATUS scan_status; ProfileItem *item; Size count = hash_get_num_entries(profile_hash); shm_mq_result mq_result; mq_result = shm_mq_send(mqh, sizeof(count), &count, false); if (mq_result == SHM_MQ_DETACHED) { ereport(WARNING, (errmsg("pg_wait_sampling collector: " "receiver of message queue has been detached"))); return; } hash_seq_init(&scan_status, profile_hash); while ((item = (ProfileItem *) hash_seq_search(&scan_status)) != NULL) { mq_result = shm_mq_send(mqh, sizeof(ProfileItem), item, false); if (mq_result == SHM_MQ_DETACHED) { hash_seq_term(&scan_status); ereport(WARNING, (errmsg("pg_wait_sampling collector: " "receiver of message queue has been detached"))); return; } } } /* * Make hash table for wait profile. */ static HTAB * make_profile_hash() { HASHCTL hash_ctl; hash_ctl.hash = tag_hash; hash_ctl.hcxt = TopMemoryContext; if (collector_hdr->profileQueries) hash_ctl.keysize = offsetof(ProfileItem, count); else hash_ctl.keysize = offsetof(ProfileItem, queryId); hash_ctl.entrysize = sizeof(ProfileItem); return hash_create("Waits profile hash", 1024, &hash_ctl, HASH_FUNCTION | HASH_ELEM); } /* * Delta between two timestamps in milliseconds. */ static int64 millisecs_diff(TimestampTz tz1, TimestampTz tz2) { long secs; int microsecs; TimestampDifference(tz1, tz2, &secs, µsecs); return secs * 1000 + microsecs / 1000; } /* * Main routine of wait history collector. */ void collector_main(Datum main_arg) { HTAB *profile_hash = NULL; History observations; MemoryContext old_context, collector_context; TimestampTz current_ts, history_ts, profile_ts; /* * Establish signal handlers. * * We want CHECK_FOR_INTERRUPTS() to kill off this worker process just as * it would a normal user backend. To make that happen, we establish a * signal handler that is a stripped-down version of die(). We don't have * any equivalent of the backend's command-read loop, where interrupts can * be processed immediately, so make sure ImmediateInterruptOK is turned * off. */ pqsignal(SIGTERM, handle_sigterm); BackgroundWorkerUnblockSignals(); #if PG_VERSION_NUM >= 110000 InitPostgres(NULL, InvalidOid, NULL, InvalidOid, NULL, false); #else InitPostgres(NULL, InvalidOid, NULL, InvalidOid, NULL); #endif SetProcessingMode(NormalProcessing); /* Make pg_wait_sampling recognisable in pg_stat_activity */ pgstat_report_appname("pg_wait_sampling collector"); profile_hash = make_profile_hash(); collector_hdr->latch = &MyProc->procLatch; CurrentResourceOwner = ResourceOwnerCreate(NULL, "pg_wait_sampling collector"); collector_context = AllocSetContextCreate(TopMemoryContext, "pg_wait_sampling context", ALLOCSET_DEFAULT_SIZES); old_context = MemoryContextSwitchTo(collector_context); alloc_history(&observations, collector_hdr->historySize); MemoryContextSwitchTo(old_context); ereport(LOG, (errmsg("pg_wait_sampling collector started"))); /* Start counting time for history and profile samples */ profile_ts = history_ts = GetCurrentTimestamp(); while (1) { int rc; shm_mq_handle *mqh; int64 history_diff, profile_diff; int history_period, profile_period; bool write_history, write_profile; /* Wait calculate time to next sample for history or profile */ current_ts = GetCurrentTimestamp(); history_diff = millisecs_diff(history_ts, current_ts); profile_diff = millisecs_diff(profile_ts, current_ts); history_period = collector_hdr->historyPeriod; profile_period = collector_hdr->profilePeriod; write_history = (history_diff >= (int64)history_period); write_profile = (profile_diff >= (int64)profile_period); if (write_history || write_profile) { probe_waits(&observations, profile_hash, write_history, write_profile, collector_hdr->profilePid); if (write_history) { history_ts = current_ts; history_diff = 0; } if (write_profile) { profile_ts = current_ts; profile_diff = 0; } } /* Shutdown if requested */ if (shutdown_requested) break; /* * Wait until next sample time or request to do something through * shared memory. */ #if PG_VERSION_NUM >= 100000 rc = WaitLatch(&MyProc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, Min(history_period - (int)history_diff, profile_period - (int)profile_diff), PG_WAIT_EXTENSION); #else rc = WaitLatch(&MyProc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, Min(history_period - (int)history_diff, profile_period - (int)profile_diff)); #endif if (rc & WL_POSTMASTER_DEATH) proc_exit(1); ResetLatch(&MyProc->procLatch); /* Handle request if any */ if (collector_hdr->request != NO_REQUEST) { LOCKTAG tag; SHMRequest request = collector_hdr->request; init_lock_tag(&tag, PGWS_COLLECTOR_LOCK); LockAcquire(&tag, ExclusiveLock, false, false); collector_hdr->request = NO_REQUEST; PG_TRY(); { if (request == HISTORY_REQUEST || request == PROFILE_REQUEST) { shm_mq_result mq_result; /* Send history or profile */ shm_mq_set_sender(collector_mq, MyProc); mqh = shm_mq_attach(collector_mq, NULL, NULL); mq_result = shm_mq_wait_for_attach(mqh); switch (mq_result) { case SHM_MQ_SUCCESS: switch (request) { case HISTORY_REQUEST: send_history(&observations, mqh); break; case PROFILE_REQUEST: send_profile(profile_hash, mqh); break; default: AssertState(false); } break; case SHM_MQ_DETACHED: ereport(WARNING, (errmsg("pg_wait_sampling collector: " "receiver of message queue has been " "detached"))); break; default: AssertState(false); } shm_mq_detach_compat(mqh, collector_mq); } else if (request == PROFILE_RESET) { /* Reset profile hash */ hash_destroy(profile_hash); profile_hash = make_profile_hash(); } LockRelease(&tag, ExclusiveLock, false); } PG_CATCH(); { LockRelease(&tag, ExclusiveLock, false); PG_RE_THROW(); } PG_END_TRY(); } } MemoryContextReset(collector_context); /* * We're done. Explicitly detach the shared memory segment so that we * don't get a resource leak warning at commit time. This will fire any * on_dsm_detach callbacks we've registered, as well. Once that's done, * we can go ahead and exit. */ ereport(LOG, (errmsg("pg_wait_sampling collector shutting down"))); proc_exit(0); } pg_wait_sampling-1.1.3/compat.c000066400000000000000000000006731400423725600165000ustar00rootroot00000000000000#include "postgres.h" #include "access/tupdesc.h" #include "pg_wait_sampling.h" inline void shm_mq_detach_compat(shm_mq_handle *mqh, shm_mq *mq) { #if PG_VERSION_NUM >= 100000 shm_mq_detach(mqh); #else shm_mq_detach(mq); #endif } inline TupleDesc CreateTemplateTupleDescCompat(int nattrs, bool hasoid) { #if PG_VERSION_NUM >= 120000 return CreateTemplateTupleDesc(nattrs); #else return CreateTemplateTupleDesc(nattrs, hasoid); #endif } pg_wait_sampling-1.1.3/conf.add000066400000000000000000000000561400423725600164430ustar00rootroot00000000000000shared_preload_libraries = 'pg_wait_sampling' pg_wait_sampling-1.1.3/debian/000077500000000000000000000000001400423725600162655ustar00rootroot00000000000000pg_wait_sampling-1.1.3/debian/changelog000066400000000000000000000005151400423725600201400ustar00rootroot00000000000000pg-wait-sampling (1.1.2-1) unstable; urgency=medium * New upstream version compatible with PG13 -- Adrien Nayrat Wed, 28 Oct 2020 09:03:03 +0000 pg-wait-sampling (1.1.1-1) unstable; urgency=medium * Release 1.1.1 -- Adrien Nayrat Wed, 17 Jun 2020 12:26:59 +0000 pg_wait_sampling-1.1.3/debian/compat000066400000000000000000000000021400423725600174630ustar00rootroot000000000000009 pg_wait_sampling-1.1.3/debian/control000066400000000000000000000031411400423725600176670ustar00rootroot00000000000000Source: pg-wait-sampling Section: database Priority: optional Maintainer: Adrien Nayrat Standards-Version: 4.5.0 Build-Depends: debhelper (>=9~), postgresql-server-dev-all (>= 141~) Homepage: https://github.com/postgrespro/pg_wait_sampling Vcs-Browser: https://github.com/postgrespro/pg_wait_sampling Vcs-Git: https://github.com/postgrespro/pg_wait_sampling.git Package: postgresql-9.6-pg-wait-sampling Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, postgresql-9.6, Description: pg_wait-sampling provides functions for detailed per backend and per query statistics about PostgreSQL wait events Package: postgresql-10-pg-wait-sampling Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, postgresql-10, Description: pg_wait-sampling provides functions for detailed per backend and per query statistics about PostgreSQL wait events Package: postgresql-11-pg-wait-sampling Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, postgresql-11, Description: pg_wait-sampling provides functions for detailed per backend and per query statistics about PostgreSQL wait events Package: postgresql-12-pg-wait-sampling Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, postgresql-12, Description: pg_wait-sampling provides functions for detailed per backend and per query statistics about PostgreSQL wait events Package: postgresql-13-pg-wait-sampling Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, postgresql-13, Description: pg_wait-sampling provides functions for detailed per backend and per query statistics about PostgreSQL wait events pg_wait_sampling-1.1.3/debian/control.in000066400000000000000000000012151400423725600202740ustar00rootroot00000000000000Source: pg-wait-sampling Section: database Priority: optional Maintainer: Adrien Nayrat Standards-Version: 4.5.0 Build-Depends: debhelper (>=9~), postgresql-server-dev-all (>= 141~) Homepage: https://github.com/postgrespro/pg_wait_sampling Vcs-Browser: https://github.com/postgrespro/pg_wait_sampling Vcs-Git: https://github.com/postgrespro/pg_wait_sampling.git Package: postgresql-PGVERSION-pg-wait-sampling Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, postgresql-PGVERSION, Description: pg_wait-sampling provides functions for detailed per backend and per query statistics about PostgreSQL wait events pg_wait_sampling-1.1.3/debian/copyright000066400000000000000000000023401400423725600202170ustar00rootroot00000000000000pg_wait_sampling is released under the PostgreSQL License, a liberal Open Source license, similar to the BSD or MIT licenses. Copyright (c) 2015-2017, Postgres Professional Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group Portions Copyright (c) 1994, The Regents of the University of California Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. IN NO EVENT SHALL POSTGRES PROFESSIONAL BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF POSTGRES PROFESSIONAL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. POSTGRES PROFESSIONAL SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND POSTGRES PROFESSIONAL HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. pg_wait_sampling-1.1.3/debian/pgversions000066400000000000000000000000051400423725600204020ustar00rootroot000000000000009.6+ pg_wait_sampling-1.1.3/debian/rules000077500000000000000000000013211400423725600173420ustar00rootroot00000000000000#!/usr/bin/make -f PKGVER = $(shell dpkg-parsechangelog | awk -F '[:-]' '/^Version:/ { print substr($$2, 2) }') EXCLUDE = --exclude-vcs --exclude=debian include /usr/share/postgresql-common/pgxs_debian_control.mk override_dh_auto_build: # do nothing override_dh_auto_test: # nothing to do here, upstream tests used, see debian/tests/* override_dh_auto_install: # build all supported versions +pg_buildext loop postgresql-%v-pg-wait-sampling override_dh_installdocs: dh_installdocs --all README.md override_dh_auto_clean: $(MAKE) clean USE_PGXS=1 PG_CONFIG=/usr/bin/pg_config orig: debian/control clean cd .. && tar czf pg-wait-sampling_$(PKGVER).orig.tar.gz $(EXCLUDE) pg_wait_sampling-$(PKGVER) %: dh $@ pg_wait_sampling-1.1.3/debian/source/000077500000000000000000000000001400423725600175655ustar00rootroot00000000000000pg_wait_sampling-1.1.3/debian/source/format000066400000000000000000000000141400423725600207730ustar00rootroot000000000000003.0 (quilt) pg_wait_sampling-1.1.3/debian/tests/000077500000000000000000000000001400423725600174275ustar00rootroot00000000000000pg_wait_sampling-1.1.3/debian/tests/control000066400000000000000000000001251400423725600210300ustar00rootroot00000000000000Depends: @, postgresql-server-dev-all Tests: installcheck Restrictions: allow-stderr pg_wait_sampling-1.1.3/debian/tests/installcheck000077500000000000000000000001351400423725600220200ustar00rootroot00000000000000#!/bin/sh set -eu pg_buildext -o "shared_preload_libraries=pg_wait_sampling" installcheck pg_wait_sampling-1.1.3/debian/watch000066400000000000000000000001751400423725600173210ustar00rootroot00000000000000version=3 opts="uversionmangle=s/_/./g" \ https://github.com/postgrespro/pg_wait_sampling/releases .*/archive/v(.*).tar.gz pg_wait_sampling-1.1.3/docker-compose.yml000066400000000000000000000000241400423725600204740ustar00rootroot00000000000000tests: build: . pg_wait_sampling-1.1.3/expected/000077500000000000000000000000001400423725600166445ustar00rootroot00000000000000pg_wait_sampling-1.1.3/expected/load.out000066400000000000000000000016751400423725600203250ustar00rootroot00000000000000CREATE EXTENSION pg_wait_sampling; \d pg_wait_sampling_current View "public.pg_wait_sampling_current" Column | Type | Modifiers ------------+---------+----------- pid | integer | event_type | text | event | text | queryid | bigint | \d pg_wait_sampling_history View "public.pg_wait_sampling_history" Column | Type | Modifiers ------------+--------------------------+----------- pid | integer | ts | timestamp with time zone | event_type | text | event | text | queryid | bigint | \d pg_wait_sampling_profile View "public.pg_wait_sampling_profile" Column | Type | Modifiers ------------+---------+----------- pid | integer | event_type | text | event | text | queryid | bigint | count | bigint | DROP EXTENSION pg_wait_sampling; pg_wait_sampling-1.1.3/expected/load_1.out000066400000000000000000000026301400423725600205350ustar00rootroot00000000000000CREATE EXTENSION pg_wait_sampling; \d pg_wait_sampling_current View "public.pg_wait_sampling_current" Column | Type | Collation | Nullable | Default ------------+---------+-----------+----------+--------- pid | integer | | | event_type | text | | | event | text | | | queryid | bigint | | | \d pg_wait_sampling_history View "public.pg_wait_sampling_history" Column | Type | Collation | Nullable | Default ------------+--------------------------+-----------+----------+--------- pid | integer | | | ts | timestamp with time zone | | | event_type | text | | | event | text | | | queryid | bigint | | | \d pg_wait_sampling_profile View "public.pg_wait_sampling_profile" Column | Type | Collation | Nullable | Default ------------+---------+-----------+----------+--------- pid | integer | | | event_type | text | | | event | text | | | queryid | bigint | | | count | bigint | | | DROP EXTENSION pg_wait_sampling; pg_wait_sampling-1.1.3/expected/queries.out000066400000000000000000000016641400423725600210610ustar00rootroot00000000000000CREATE EXTENSION pg_wait_sampling; WITH t as (SELECT sum(0) FROM pg_wait_sampling_current) SELECT sum(0) FROM generate_series(1, 2), t; sum ----- 0 (1 row) WITH t as (SELECT sum(0) FROM pg_wait_sampling_history) SELECT sum(0) FROM generate_series(1, 2), t; sum ----- 0 (1 row) WITH t as (SELECT sum(0) FROM pg_wait_sampling_profile) SELECT sum(0) FROM generate_series(1, 2), t; sum ----- 0 (1 row) -- Some dummy checks just to be sure that all our functions work and return something. SELECT count(*) = 1 as test FROM pg_wait_sampling_get_current(pg_backend_pid()); test ------ t (1 row) SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_profile(); test ------ t (1 row) SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_history(); test ------ t (1 row) SELECT pg_wait_sampling_reset_profile(); pg_wait_sampling_reset_profile -------------------------------- (1 row) DROP EXTENSION pg_wait_sampling; pg_wait_sampling-1.1.3/pg_wait_sampling--1.0--1.1.sql000066400000000000000000000027561400423725600220450ustar00rootroot00000000000000/* contrib/pg_wait_sampling/pg_wait_sampling--1.0--1.1.sql */ DROP FUNCTION pg_wait_sampling_get_current ( pid int4, OUT pid int4, OUT event_type text, OUT event text ) CASCADE; DROP FUNCTION pg_wait_sampling_get_history ( OUT pid int4, OUT ts timestamptz, OUT event_type text, OUT event text ) CASCADE; DROP FUNCTION pg_wait_sampling_get_profile ( OUT pid int4, OUT event_type text, OUT event text, OUT count bigint ) CASCADE; CREATE FUNCTION pg_wait_sampling_get_current ( pid int4, OUT pid int4, OUT event_type text, OUT event text, OUT queryid int8 ) RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C VOLATILE CALLED ON NULL INPUT; CREATE VIEW pg_wait_sampling_current AS SELECT * FROM pg_wait_sampling_get_current(NULL::integer); GRANT SELECT ON pg_wait_sampling_current TO PUBLIC; CREATE FUNCTION pg_wait_sampling_get_history ( OUT pid int4, OUT ts timestamptz, OUT event_type text, OUT event text, OUT queryid int8 ) RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C VOLATILE STRICT; CREATE VIEW pg_wait_sampling_history AS SELECT * FROM pg_wait_sampling_get_history(); GRANT SELECT ON pg_wait_sampling_history TO PUBLIC; CREATE FUNCTION pg_wait_sampling_get_profile ( OUT pid int4, OUT event_type text, OUT event text, OUT queryid int8, OUT count int8 ) RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C VOLATILE STRICT; CREATE VIEW pg_wait_sampling_profile AS SELECT * FROM pg_wait_sampling_get_profile(); GRANT SELECT ON pg_wait_sampling_profile TO PUBLIC; pg_wait_sampling-1.1.3/pg_wait_sampling.c000066400000000000000000000523421400423725600205410ustar00rootroot00000000000000/* * pg_wait_sampling.c * Track information about wait events. * * Copyright (c) 2015-2017, Postgres Professional * * IDENTIFICATION * contrib/pg_wait_sampling/pg_wait_sampling.c */ #include "postgres.h" #include "access/htup_details.h" #include "access/twophase.h" #include "catalog/pg_type.h" #include "fmgr.h" #include "funcapi.h" #include "miscadmin.h" #include "optimizer/planner.h" #include "pgstat.h" #include "storage/ipc.h" #include "storage/pg_shmem.h" #include "storage/procarray.h" #include "storage/shm_mq.h" #include "storage/shm_toc.h" #include "storage/spin.h" #include "utils/builtins.h" #include "utils/datetime.h" #include "utils/guc_tables.h" #include "utils/guc.h" #include "utils/memutils.h" /* TopMemoryContext. Actually for PG 9.6 only, * but there should be no harm for others. */ #include "pg_wait_sampling.h" PG_MODULE_MAGIC; void _PG_init(void); void _PG_fini(void); /* Global variables */ bool shmem_initialized = false; /* Hooks */ static ExecutorEnd_hook_type prev_ExecutorEnd = NULL; static planner_hook_type planner_hook_next = NULL; /* Shared memory variables */ shm_toc *toc = NULL; shm_mq *collector_mq = NULL; uint64 *proc_queryids = NULL; CollectorShmqHeader *collector_hdr = NULL; /* Receiver (backend) local shm_mq pointers and lock */ shm_mq *recv_mq = NULL; shm_mq_handle *recv_mqh = NULL; LOCKTAG queueTag; static shmem_startup_hook_type prev_shmem_startup_hook = NULL; static PGPROC * search_proc(int backendPid); static PlannedStmt *pgws_planner_hook(Query *parse, #if PG_VERSION_NUM >= 130000 const char *query_string, #endif int cursorOptions, ParamListInfo boundParams); static void pgws_ExecutorEnd(QueryDesc *queryDesc); /* * Calculate max processes count. * Look at InitProcGlobal (proc.c) and TotalProcs variable in it * if something wrong here. */ static int get_max_procs_count(void) { int count = 0; /* MyProcs, including autovacuum workers and launcher */ count += MaxBackends; /* AuxiliaryProcs */ count += NUM_AUXILIARY_PROCS; /* Prepared xacts */ count += max_prepared_xacts; return count; } /* * Estimate amount of shared memory needed. */ static Size pgws_shmem_size(void) { shm_toc_estimator e; Size size; int nkeys; shm_toc_initialize_estimator(&e); nkeys = 3; shm_toc_estimate_chunk(&e, sizeof(CollectorShmqHeader)); shm_toc_estimate_chunk(&e, (Size) COLLECTOR_QUEUE_SIZE); shm_toc_estimate_chunk(&e, sizeof(uint64) * get_max_procs_count()); shm_toc_estimate_keys(&e, nkeys); size = shm_toc_estimate(&e); return size; } static bool shmem_int_guc_check_hook(int *newval, void **extra, GucSource source) { if (UsedShmemSegAddr == NULL) return false; return true; } static bool shmem_bool_guc_check_hook(bool *newval, void **extra, GucSource source) { if (UsedShmemSegAddr == NULL) return false; return true; } /* * This union allows us to mix the numerous different types of structs * that we are organizing. */ typedef union { struct config_generic generic; struct config_bool _bool; struct config_real real; struct config_int integer; struct config_string string; struct config_enum _enum; } mixedStruct; /* * Setup new GUCs or modify existsing. */ static void setup_gucs() { struct config_generic **guc_vars; int numOpts, i; bool history_size_found = false, history_period_found = false, profile_period_found = false, profile_pid_found = false, profile_queries_found = false; guc_vars = get_guc_variables(); numOpts = GetNumConfigOptions(); for (i = 0; i < numOpts; i++) { mixedStruct *var = (mixedStruct *) guc_vars[i]; const char *name = var->generic.name; if (var->generic.flags & GUC_CUSTOM_PLACEHOLDER) continue; if (!strcmp(name, "pg_wait_sampling.history_size")) { history_size_found = true; var->integer.variable = &collector_hdr->historySize; collector_hdr->historySize = 5000; } else if (!strcmp(name, "pg_wait_sampling.history_period")) { history_period_found = true; var->integer.variable = &collector_hdr->historyPeriod; collector_hdr->historyPeriod = 10; } else if (!strcmp(name, "pg_wait_sampling.profile_period")) { profile_period_found = true; var->integer.variable = &collector_hdr->profilePeriod; collector_hdr->profilePeriod = 10; } else if (!strcmp(name, "pg_wait_sampling.profile_pid")) { profile_pid_found = true; var->_bool.variable = &collector_hdr->profilePid; collector_hdr->profilePid = true; } else if (!strcmp(name, "pg_wait_sampling.profile_queries")) { profile_queries_found = true; var->_bool.variable = &collector_hdr->profileQueries; collector_hdr->profileQueries = true; } } if (!history_size_found) DefineCustomIntVariable("pg_wait_sampling.history_size", "Sets size of waits history.", NULL, &collector_hdr->historySize, 5000, 100, INT_MAX, PGC_SUSET, 0, shmem_int_guc_check_hook, NULL, NULL); if (!history_period_found) DefineCustomIntVariable("pg_wait_sampling.history_period", "Sets period of waits history sampling.", NULL, &collector_hdr->historyPeriod, 10, 1, INT_MAX, PGC_SUSET, 0, shmem_int_guc_check_hook, NULL, NULL); if (!profile_period_found) DefineCustomIntVariable("pg_wait_sampling.profile_period", "Sets period of waits profile sampling.", NULL, &collector_hdr->profilePeriod, 10, 1, INT_MAX, PGC_SUSET, 0, shmem_int_guc_check_hook, NULL, NULL); if (!profile_pid_found) DefineCustomBoolVariable("pg_wait_sampling.profile_pid", "Sets whether profile should be collected per pid.", NULL, &collector_hdr->profilePid, true, PGC_SUSET, 0, shmem_bool_guc_check_hook, NULL, NULL); if (!profile_queries_found) DefineCustomBoolVariable("pg_wait_sampling.profile_queries", "Sets whether profile should be collected per query.", NULL, &collector_hdr->profileQueries, true, PGC_SUSET, 0, shmem_bool_guc_check_hook, NULL, NULL); if (history_size_found || history_period_found || profile_period_found || profile_pid_found || profile_queries_found) { ProcessConfigFile(PGC_SIGHUP); } } /* * Distribute shared memory. */ static void pgws_shmem_startup(void) { bool found; Size segsize = pgws_shmem_size(); void *pgws; pgws = ShmemInitStruct("pg_wait_sampling", segsize, &found); if (!found) { toc = shm_toc_create(PG_WAIT_SAMPLING_MAGIC, pgws, segsize); collector_hdr = shm_toc_allocate(toc, sizeof(CollectorShmqHeader)); shm_toc_insert(toc, 0, collector_hdr); collector_mq = shm_toc_allocate(toc, COLLECTOR_QUEUE_SIZE); shm_toc_insert(toc, 1, collector_mq); proc_queryids = shm_toc_allocate(toc, sizeof(uint64) * get_max_procs_count()); shm_toc_insert(toc, 2, proc_queryids); MemSet(proc_queryids, 0, sizeof(uint64) * get_max_procs_count()); /* Initialize GUC variables in shared memory */ setup_gucs(); } else { toc = shm_toc_attach(PG_WAIT_SAMPLING_MAGIC, pgws); #if PG_VERSION_NUM >= 100000 collector_hdr = shm_toc_lookup(toc, 0, false); collector_mq = shm_toc_lookup(toc, 1, false); proc_queryids = shm_toc_lookup(toc, 2, false); #else collector_hdr = shm_toc_lookup(toc, 0); collector_mq = shm_toc_lookup(toc, 1); proc_queryids = shm_toc_lookup(toc, 2); #endif } shmem_initialized = true; if (prev_shmem_startup_hook) prev_shmem_startup_hook(); } /* * Check shared memory is initialized. Report an error otherwise. */ void check_shmem(void) { if (!shmem_initialized) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("pg_wait_sampling shared memory wasn't initialized yet"))); } } static void pgws_cleanup_callback(int code, Datum arg) { elog(DEBUG3, "pg_wait_sampling cleanup: detaching shm_mq and releasing queue lock"); shm_mq_detach_compat(recv_mqh, recv_mq); LockRelease(&queueTag, ExclusiveLock, false); } /* * Module load callback */ void _PG_init(void) { if (!process_shared_preload_libraries_in_progress) return; /* * Request additional shared resources. (These are no-ops if we're not in * the postmaster process.) We'll allocate or attach to the shared * resources in pgws_shmem_startup(). */ RequestAddinShmemSpace(pgws_shmem_size()); register_wait_collector(); /* * Install hooks. */ prev_shmem_startup_hook = shmem_startup_hook; shmem_startup_hook = pgws_shmem_startup; planner_hook_next = planner_hook; planner_hook = pgws_planner_hook; prev_ExecutorEnd = ExecutorEnd_hook; ExecutorEnd_hook = pgws_ExecutorEnd; } /* * Module unload callback */ void _PG_fini(void) { /* Uninstall hooks. */ shmem_startup_hook = prev_shmem_startup_hook; } /* * Find PGPROC entry responsible for given pid assuming ProcArrayLock was * already taken. */ static PGPROC * search_proc(int pid) { int i; if (pid == 0) return MyProc; for (i = 0; i < ProcGlobal->allProcCount; i++) { PGPROC *proc = &ProcGlobal->allProcs[i]; if (proc->pid && proc->pid == pid) { return proc; } } ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("backend with pid=%d not found", pid))); return NULL; } typedef struct { HistoryItem *items; TimestampTz ts; } WaitCurrentContext; PG_FUNCTION_INFO_V1(pg_wait_sampling_get_current); Datum pg_wait_sampling_get_current(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; WaitCurrentContext *params; check_shmem(); if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; TupleDesc tupdesc; WaitCurrentContext *params; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); params = (WaitCurrentContext *)palloc0(sizeof(WaitCurrentContext)); params->ts = GetCurrentTimestamp(); funcctx->user_fctx = params; tupdesc = CreateTemplateTupleDescCompat(4, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", INT8OID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); LWLockAcquire(ProcArrayLock, LW_SHARED); if (!PG_ARGISNULL(0)) { HistoryItem *item; PGPROC *proc; proc = search_proc(PG_GETARG_UINT32(0)); params->items = (HistoryItem *) palloc0(sizeof(HistoryItem)); item = ¶ms->items[0]; item->pid = proc->pid; item->wait_event_info = proc->wait_event_info; item->queryId = proc_queryids[proc - ProcGlobal->allProcs]; funcctx->max_calls = 1; } else { int procCount = ProcGlobal->allProcCount, i, j = 0; params->items = (HistoryItem *) palloc0(sizeof(HistoryItem) * procCount); for (i = 0; i < procCount; i++) { PGPROC *proc = &ProcGlobal->allProcs[i]; if (proc != NULL && proc->pid != 0 && proc->wait_event_info) { params->items[j].pid = proc->pid; params->items[j].wait_event_info = proc->wait_event_info; params->items[j].queryId = proc_queryids[i]; j++; } } funcctx->max_calls = j; } LWLockRelease(ProcArrayLock); MemoryContextSwitchTo(oldcontext); } /* stuff done on every call of the function */ funcctx = SRF_PERCALL_SETUP(); params = (WaitCurrentContext *) funcctx->user_fctx; if (funcctx->call_cntr < funcctx->max_calls) { HeapTuple tuple; Datum values[4]; bool nulls[4]; const char *event_type, *event; HistoryItem *item; item = ¶ms->items[funcctx->call_cntr]; /* Make and return next tuple to caller */ MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); event_type = pgstat_get_wait_event_type(item->wait_event_info); event = pgstat_get_wait_event(item->wait_event_info); values[0] = Int32GetDatum(item->pid); if (event_type) values[1] = PointerGetDatum(cstring_to_text(event_type)); else nulls[1] = true; if (event) values[2] = PointerGetDatum(cstring_to_text(event)); else nulls[2] = true; values[3] = Int64GetDatumFast(item->queryId); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); } else { SRF_RETURN_DONE(funcctx); } } typedef struct { Size count; ProfileItem *items; } Profile; void init_lock_tag(LOCKTAG *tag, uint32 lock) { tag->locktag_field1 = PG_WAIT_SAMPLING_MAGIC; tag->locktag_field2 = lock; tag->locktag_field3 = 0; tag->locktag_field4 = 0; tag->locktag_type = LOCKTAG_USERLOCK; tag->locktag_lockmethodid = USER_LOCKMETHOD; } static void * receive_array(SHMRequest request, Size item_size, Size *count) { LOCKTAG collectorTag; shm_mq_result res; Size len, i; void *data; Pointer result, ptr; MemoryContext oldctx; /* Ensure nobody else trying to send request to queue */ init_lock_tag(&queueTag, PGWS_QUEUE_LOCK); LockAcquire(&queueTag, ExclusiveLock, false, false); /* Ensure collector has processed previous request */ init_lock_tag(&collectorTag, PGWS_COLLECTOR_LOCK); LockAcquire(&collectorTag, ExclusiveLock, false, false); LockRelease(&collectorTag, ExclusiveLock, false); recv_mq = shm_mq_create(collector_mq, COLLECTOR_QUEUE_SIZE); collector_hdr->request = request; if (!collector_hdr->latch) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("pg_wait_sampling collector wasn't started"))); SetLatch(collector_hdr->latch); shm_mq_set_receiver(recv_mq, MyProc); /* * We switch to TopMemoryContext, so that recv_mqh is allocated there * and is guaranteed to survive until before_shmem_exit callbacks are * fired. Anyway, shm_mq_detach() will free handler on its own. * * NB: we do not pass `seg` to shm_mq_attach(), so it won't set its own * callback, i.e. we do not interfere here with shm_mq_detach_callback(). */ oldctx = MemoryContextSwitchTo(TopMemoryContext); recv_mqh = shm_mq_attach(recv_mq, NULL, NULL); MemoryContextSwitchTo(oldctx); /* * Now we surely attached to the shm_mq and got collector's attention. * If anything went wrong (e.g. Ctrl+C received from the client) we have * to cleanup some things, i.e. detach from the shm_mq, so collector was * able to continue responding to other requests. * * PG_ENSURE_ERROR_CLEANUP() guaranties that cleanup callback will be * fired for both ERROR and FATAL. */ PG_ENSURE_ERROR_CLEANUP(pgws_cleanup_callback, 0); { res = shm_mq_receive(recv_mqh, &len, &data, false); if (res != SHM_MQ_SUCCESS || len != sizeof(*count)) elog(ERROR, "error reading mq"); memcpy(count, data, sizeof(*count)); result = palloc(item_size * (*count)); ptr = result; for (i = 0; i < *count; i++) { res = shm_mq_receive(recv_mqh, &len, &data, false); if (res != SHM_MQ_SUCCESS || len != item_size) elog(ERROR, "error reading mq"); memcpy(ptr, data, item_size); ptr += item_size; } } PG_END_ENSURE_ERROR_CLEANUP(pgws_cleanup_callback, 0); /* We still have to detach and release lock during normal operation. */ shm_mq_detach_compat(recv_mqh, recv_mq); LockRelease(&queueTag, ExclusiveLock, false); return result; } PG_FUNCTION_INFO_V1(pg_wait_sampling_get_profile); Datum pg_wait_sampling_get_profile(PG_FUNCTION_ARGS) { Profile *profile; FuncCallContext *funcctx; check_shmem(); if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; TupleDesc tupdesc; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* Receive profile from shmq */ profile = (Profile *) palloc0(sizeof(Profile)); profile->items = (ProfileItem *) receive_array(PROFILE_REQUEST, sizeof(ProfileItem), &profile->count); funcctx->user_fctx = profile; funcctx->max_calls = profile->count; /* Make tuple descriptor */ tupdesc = CreateTemplateTupleDescCompat(5, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 3, "event", TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queryid", INT8OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 5, "count", INT8OID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); MemoryContextSwitchTo(oldcontext); } /* stuff done on every call of the function */ funcctx = SRF_PERCALL_SETUP(); profile = (Profile *) funcctx->user_fctx; if (funcctx->call_cntr < funcctx->max_calls) { /* for each row */ Datum values[5]; bool nulls[5]; HeapTuple tuple; ProfileItem *item; const char *event_type, *event; item = &profile->items[funcctx->call_cntr]; MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); /* Make and return next tuple to caller */ event_type = pgstat_get_wait_event_type(item->wait_event_info); event = pgstat_get_wait_event(item->wait_event_info); values[0] = Int32GetDatum(item->pid); if (event_type) values[1] = PointerGetDatum(cstring_to_text(event_type)); else nulls[1] = true; if (event) values[2] = PointerGetDatum(cstring_to_text(event)); else nulls[2] = true; if (collector_hdr->profileQueries) values[3] = Int64GetDatumFast(item->queryId); else values[3] = (Datum) 0; values[4] = Int64GetDatumFast(item->count); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); } else { /* nothing left */ SRF_RETURN_DONE(funcctx); } } PG_FUNCTION_INFO_V1(pg_wait_sampling_reset_profile); Datum pg_wait_sampling_reset_profile(PG_FUNCTION_ARGS) { LOCKTAG tag; LOCKTAG tagCollector; check_shmem(); init_lock_tag(&tag, PGWS_QUEUE_LOCK); LockAcquire(&tag, ExclusiveLock, false, false); init_lock_tag(&tagCollector, PGWS_COLLECTOR_LOCK); LockAcquire(&tagCollector, ExclusiveLock, false, false); LockRelease(&tagCollector, ExclusiveLock, false); collector_hdr->request = PROFILE_RESET; SetLatch(collector_hdr->latch); LockRelease(&tag, ExclusiveLock, false); PG_RETURN_VOID(); } PG_FUNCTION_INFO_V1(pg_wait_sampling_get_history); Datum pg_wait_sampling_get_history(PG_FUNCTION_ARGS) { History *history; FuncCallContext *funcctx; check_shmem(); if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; TupleDesc tupdesc; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* Receive history from shmq */ history = (History *) palloc0(sizeof(History)); history->items = (HistoryItem *) receive_array(HISTORY_REQUEST, sizeof(HistoryItem), &history->count); funcctx->user_fctx = history; funcctx->max_calls = history->count; /* Make tuple descriptor */ tupdesc = CreateTemplateTupleDescCompat(5, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "sample_ts", TIMESTAMPTZOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 3, "type", TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 4, "event", TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 5, "queryid", INT8OID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); MemoryContextSwitchTo(oldcontext); } /* stuff done on every call of the function */ funcctx = SRF_PERCALL_SETUP(); history = (History *) funcctx->user_fctx; if (history->index < history->count) { HeapTuple tuple; HistoryItem *item; Datum values[5]; bool nulls[5]; const char *event_type, *event; item = &history->items[history->index]; /* Make and return next tuple to caller */ MemSet(values, 0, sizeof(values)); MemSet(nulls, 0, sizeof(nulls)); event_type = pgstat_get_wait_event_type(item->wait_event_info); event = pgstat_get_wait_event(item->wait_event_info); values[0] = Int32GetDatum(item->pid); values[1] = TimestampTzGetDatum(item->ts); if (event_type) values[2] = PointerGetDatum(cstring_to_text(event_type)); else nulls[2] = true; if (event) values[3] = PointerGetDatum(cstring_to_text(event)); else nulls[3] = true; values[4] = Int64GetDatumFast(item->queryId); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); history->index++; SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); } else { /* nothing left */ SRF_RETURN_DONE(funcctx); } PG_RETURN_VOID(); } /* * planner_hook hook, save queryId for collector */ static PlannedStmt * pgws_planner_hook(Query *parse, #if PG_VERSION_NUM >= 130000 const char *query_string, #endif int cursorOptions, ParamListInfo boundParams) { if (MyProc) { int i = MyProc - ProcGlobal->allProcs; #if PG_VERSION_NUM >= 110000 /* * since we depend on queryId we need to check that its size * is uint64 as we coded in pg_wait_sampling */ StaticAssertExpr(sizeof(parse->queryId) == sizeof(uint64), "queryId size is not uint64"); #else StaticAssertExpr(sizeof(parse->queryId) == sizeof(uint32), "queryId size is not uint32"); #endif if (!proc_queryids[i]) proc_queryids[i] = parse->queryId; } /* Invoke original hook if needed */ if (planner_hook_next) return planner_hook_next(parse, #if PG_VERSION_NUM >= 130000 query_string, #endif cursorOptions, boundParams); return standard_planner(parse, #if PG_VERSION_NUM >= 130000 query_string, #endif cursorOptions, boundParams); } /* * ExecutorEnd hook: clear queryId */ static void pgws_ExecutorEnd(QueryDesc *queryDesc) { if (MyProc) proc_queryids[MyProc - ProcGlobal->allProcs] = UINT64CONST(0); if (prev_ExecutorEnd) prev_ExecutorEnd(queryDesc); else standard_ExecutorEnd(queryDesc); } pg_wait_sampling-1.1.3/pg_wait_sampling.control000066400000000000000000000002521400423725600217700ustar00rootroot00000000000000# pg_wait_sampling extension comment = 'sampling based statistics of wait events' default_version = '1.1' module_pathname = '$libdir/pg_wait_sampling' relocatable = true pg_wait_sampling-1.1.3/pg_wait_sampling.h000066400000000000000000000036161400423725600205460ustar00rootroot00000000000000/* * pg_wait_sampling.h * Headers for pg_wait_sampling extension. * * Copyright (c) 2015-2016, Postgres Professional * * IDENTIFICATION * contrib/pg_wait_sampling/pg_wait_sampling.h */ #ifndef __PG_WAIT_SAMPLING_H__ #define __PG_WAIT_SAMPLING_H__ #include /* Check PostgreSQL version */ #if PG_VERSION_NUM < 90600 #error "You are trying to build pg_wait_sampling with PostgreSQL version lower than 9.6. Please, check you environment." #endif #include "storage/proc.h" #include "storage/shm_mq.h" #include "utils/timestamp.h" #define PG_WAIT_SAMPLING_MAGIC 0xCA94B107 #define COLLECTOR_QUEUE_SIZE (16 * 1024) #define HISTORY_TIME_MULTIPLIER 10 #define PGWS_QUEUE_LOCK 0 #define PGWS_COLLECTOR_LOCK 1 typedef struct { uint32 pid; uint32 wait_event_info; uint64 queryId; uint64 count; } ProfileItem; typedef struct { uint32 pid; uint32 wait_event_info; uint64 queryId; TimestampTz ts; } HistoryItem; typedef struct { bool wraparound; Size index; Size count; HistoryItem *items; } History; typedef enum { NO_REQUEST, HISTORY_REQUEST, PROFILE_REQUEST, PROFILE_RESET } SHMRequest; typedef struct { Latch *latch; SHMRequest request; int historySize; int historyPeriod; int profilePeriod; bool profilePid; bool profileQueries; } CollectorShmqHeader; /* pg_wait_sampling.c */ extern void check_shmem(void); extern CollectorShmqHeader *collector_hdr; extern shm_mq *collector_mq; extern uint64 *proc_queryids; extern void read_current_wait(PGPROC *proc, HistoryItem *item); extern void init_lock_tag(LOCKTAG *tag, uint32 lock); /* collector.c */ extern void register_wait_collector(void); extern void alloc_history(History *, int); extern void collector_main(Datum main_arg); extern void shm_mq_detach_compat(shm_mq_handle *mqh, shm_mq *mq); extern TupleDesc CreateTemplateTupleDescCompat(int nattrs, bool hasoid); #endif pg_wait_sampling-1.1.3/run_tests.sh000077500000000000000000000031501400423725600174270ustar00rootroot00000000000000#!/bin/bash # This is a main testing script for: # * regression tests # * testgres-based tests # * cmocka-based tests # Copyright (c) 2017, Postgres Professional set -eux echo CHECK_CODE=$CHECK_CODE status=0 # perform code analysis if necessary if [ "$CHECK_CODE" = "clang" ]; then scan-build --status-bugs make USE_PGXS=1 || status=$? exit $status elif [ "$CHECK_CODE" = "cppcheck" ]; then cppcheck \ --template "{file} ({line}): {severity} ({id}): {message}" \ --enable=warning,portability,performance \ --suppress=redundantAssignment \ --suppress=uselessAssignmentPtrArg \ --suppress=literalWithCharPtrCompare \ --suppress=incorrectStringBooleanError \ --std=c89 *.c *.h 2> cppcheck.log if [ -s cppcheck.log ]; then cat cppcheck.log status=1 # error fi exit $status fi # don't forget to "make clean" make USE_PGXS=1 clean # initialize database initdb # build extension make USE_PGXS=1 install # check build status=$? if [ $status -ne 0 ]; then exit $status; fi # add pg_wait_sampling to shared_preload_libraries and restart cluster 'test' echo "shared_preload_libraries = 'pg_wait_sampling'" >> $PGDATA/postgresql.conf echo "port = 55435" >> $PGDATA/postgresql.conf pg_ctl start -l /tmp/postgres.log -w # check startup status=$? if [ $status -ne 0 ]; then cat /tmp/postgres.log; fi # run regression tests export PG_REGRESS_DIFF_OPTS="-w -U3" # for alpine's diff (BusyBox) PGPORT=55435 make USE_PGXS=1 installcheck || status=$? # show diff if it exists if test -f regression.diffs; then cat regression.diffs; fi exit $status pg_wait_sampling-1.1.3/setup.sql000066400000000000000000000027331400423725600167310ustar00rootroot00000000000000/* contrib/pg_wait_sampling/setup.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pg_wait_sampling" to load this file. \quit CREATE FUNCTION pg_wait_sampling_get_current ( pid int4, OUT pid int4, OUT event_type text, OUT event text, OUT queryid int8 ) RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C VOLATILE CALLED ON NULL INPUT; CREATE VIEW pg_wait_sampling_current AS SELECT * FROM pg_wait_sampling_get_current(NULL::integer); GRANT SELECT ON pg_wait_sampling_current TO PUBLIC; CREATE FUNCTION pg_wait_sampling_get_history ( OUT pid int4, OUT ts timestamptz, OUT event_type text, OUT event text, OUT queryid int8 ) RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C VOLATILE STRICT; CREATE VIEW pg_wait_sampling_history AS SELECT * FROM pg_wait_sampling_get_history(); GRANT SELECT ON pg_wait_sampling_history TO PUBLIC; CREATE FUNCTION pg_wait_sampling_get_profile ( OUT pid int4, OUT event_type text, OUT event text, OUT queryid int8, OUT count int8 ) RETURNS SETOF record AS 'MODULE_PATHNAME' LANGUAGE C VOLATILE STRICT; CREATE VIEW pg_wait_sampling_profile AS SELECT * FROM pg_wait_sampling_get_profile(); GRANT SELECT ON pg_wait_sampling_profile TO PUBLIC; CREATE FUNCTION pg_wait_sampling_reset_profile() RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C VOLATILE STRICT; -- Don't want this to be available to non-superusers. REVOKE ALL ON FUNCTION pg_wait_sampling_reset_profile() FROM PUBLIC; pg_wait_sampling-1.1.3/sql/000077500000000000000000000000001400423725600156425ustar00rootroot00000000000000pg_wait_sampling-1.1.3/sql/load.sql000066400000000000000000000002321400423725600172770ustar00rootroot00000000000000CREATE EXTENSION pg_wait_sampling; \d pg_wait_sampling_current \d pg_wait_sampling_history \d pg_wait_sampling_profile DROP EXTENSION pg_wait_sampling; pg_wait_sampling-1.1.3/sql/queries.sql000066400000000000000000000013201400423725600200340ustar00rootroot00000000000000CREATE EXTENSION pg_wait_sampling; WITH t as (SELECT sum(0) FROM pg_wait_sampling_current) SELECT sum(0) FROM generate_series(1, 2), t; WITH t as (SELECT sum(0) FROM pg_wait_sampling_history) SELECT sum(0) FROM generate_series(1, 2), t; WITH t as (SELECT sum(0) FROM pg_wait_sampling_profile) SELECT sum(0) FROM generate_series(1, 2), t; -- Some dummy checks just to be sure that all our functions work and return something. SELECT count(*) = 1 as test FROM pg_wait_sampling_get_current(pg_backend_pid()); SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_profile(); SELECT count(*) >= 0 as test FROM pg_wait_sampling_get_history(); SELECT pg_wait_sampling_reset_profile(); DROP EXTENSION pg_wait_sampling;