v7 that fixes recent conflicts.

It also changed the behavior of requestor when another requestor is
already working for simplicity.
In this case, v6 patch makes the requestor wait. v7 patch makes the
requestor quit.


Regards,

--
Atsushi Torikoshi
From f20e48d99f2770bfec275805185aa5ce08661fce Mon Sep 17 00:00:00 2001
From: Atsushi Torikoshi <torikos...@oss.nttdata.com>
Date: Tue, 12 Jan 2021 20:55:43 +0900
Subject: [PATCH v7] After commit 3e98c0bafb28de, we can display the usage of
 the memory contexts using pg_backend_memory_contexts system view. However,
 its target is limited to the process attached to the current session. This
 patch introduces pg_get_target_backend_memory_contexts() and makes it
 possible to collect memory contexts of the specified process.

---
 src/backend/access/transam/xlog.c            |   7 +
 src/backend/catalog/system_views.sql         |   3 +-
 src/backend/postmaster/pgstat.c              |   3 +
 src/backend/replication/basebackup.c         |   3 +
 src/backend/storage/ipc/ipci.c               |   2 +
 src/backend/storage/ipc/procsignal.c         |   4 +
 src/backend/storage/lmgr/lwlocknames.txt     |   1 +
 src/backend/tcop/postgres.c                  |   5 +
 src/backend/utils/adt/mcxtfuncs.c            | 731 ++++++++++++++++++-
 src/backend/utils/init/globals.c             |   1 +
 src/bin/initdb/initdb.c                      |   3 +-
 src/bin/pg_basebackup/t/010_pg_basebackup.pl |   4 +-
 src/bin/pg_rewind/filemap.c                  |   3 +
 src/include/catalog/pg_proc.dat              |  12 +-
 src/include/miscadmin.h                      |   1 +
 src/include/pgstat.h                         |   3 +-
 src/include/storage/procsignal.h             |   1 +
 src/include/utils/mcxtfuncs.h                |  44 ++
 18 files changed, 810 insertions(+), 21 deletions(-)
 create mode 100644 src/include/utils/mcxtfuncs.h

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index ede93ad7fd..4cab47a61d 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -73,6 +73,7 @@
 #include "storage/sync.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
+#include "utils/mcxtfuncs.h"
 #include "utils/memutils.h"
 #include "utils/ps_status.h"
 #include "utils/relmapper.h"
@@ -6993,6 +6994,12 @@ StartupXLOG(void)
 		 */
 		pgstat_reset_all();
 
+		/*
+		 * Reset dump files in pg_memusage, because target processes do
+		 * not exist any more.
+		 */
+		RemoveMemcxtFile(0);
+
 		/*
 		 * If there was a backup label file, it's done its job and the info
 		 * has now been propagated into pg_control.  We must get rid of the
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 5d89e77dbe..7419c496b2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -558,7 +558,8 @@ CREATE VIEW pg_backend_memory_contexts AS
     SELECT * FROM pg_get_backend_memory_contexts();
 
 REVOKE ALL ON pg_backend_memory_contexts FROM PUBLIC;
-REVOKE EXECUTE ON FUNCTION pg_get_backend_memory_contexts() FROM PUBLIC;
+REVOKE EXECUTE ON FUNCTION pg_get_backend_memory_contexts FROM PUBLIC;
+REVOKE EXECUTE ON FUNCTION pg_get_target_backend_memory_contexts FROM PUBLIC;
 
 -- Statistics views
 
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 3f24a33ef1..8eb2d062b0 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -4045,6 +4045,9 @@ pgstat_get_wait_ipc(WaitEventIPC w)
 		case WAIT_EVENT_XACT_GROUP_UPDATE:
 			event_name = "XactGroupUpdate";
 			break;
+		case WAIT_EVENT_DUMP_MEMORY_CONTEXT:
+			event_name = "DumpMemoryContext";
+			break;
 			/* no default case, so that compiler will warn */
 	}
 
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 0f54635550..c67e71d79b 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -184,6 +184,9 @@ static const char *const excludeDirContents[] =
 	/* Contents zeroed on startup, see StartupSUBTRANS(). */
 	"pg_subtrans",
 
+	/* Skip memory context dump files. */
+	"pg_memusage",
+
 	/* end of list */
 	NULL
 };
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index f9bbe97b50..18a1dd5a74 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -45,6 +45,7 @@
 #include "storage/procsignal.h"
 #include "storage/sinvaladt.h"
 #include "storage/spin.h"
+#include "utils/mcxtfuncs.h"
 #include "utils/snapmgr.h"
 
 /* GUCs */
@@ -267,6 +268,7 @@ CreateSharedMemoryAndSemaphores(void)
 	BTreeShmemInit();
 	SyncScanShmemInit();
 	AsyncShmemInit();
+	McxtDumpShmemInit();
 
 #ifdef EXEC_BACKEND
 
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 583efaecff..106e125cc2 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -28,6 +28,7 @@
 #include "storage/shmem.h"
 #include "storage/sinval.h"
 #include "tcop/tcopprot.h"
+#include "utils/mcxtfuncs.h"
 
 /*
  * The SIGUSR1 signal is multiplexed to support signaling multiple event
@@ -567,6 +568,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
 	if (CheckProcSignal(PROCSIG_BARRIER))
 		HandleProcSignalBarrierInterrupt();
 
+	if (CheckProcSignal(PROCSIG_DUMP_MEMCXT))
+		HandleProcSignalDumpMemoryContext();
+
 	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_DATABASE))
 		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_DATABASE);
 
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index 774292fd94..6b4ff6f08b 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -53,3 +53,4 @@ XactTruncationLock					44
 # 45 was XactTruncationLock until removal of BackendRandomLock
 WrapLimitsVacuumLock				46
 NotifyQueueTailLock					47
+McxtDumpLock					48
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 28055680aa..986225e802 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -76,6 +76,7 @@
 #include "tcop/tcopprot.h"
 #include "tcop/utility.h"
 #include "utils/lsyscache.h"
+#include "utils/mcxtfuncs.h"
 #include "utils/memutils.h"
 #include "utils/ps_status.h"
 #include "utils/snapmgr.h"
@@ -540,6 +541,10 @@ ProcessClientReadInterrupt(bool blocked)
 		/* Process notify interrupts, if any */
 		if (notifyInterruptPending)
 			ProcessNotifyInterrupt();
+
+		/* Process memory contexts dump interrupts, if any */
+		if (ProcSignalDumpMemoryContextPending)
+			ProcessDumpMemoryContextInterrupt();
 	}
 	else if (ProcDiePending)
 	{
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index c02fa47550..71afef0904 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -15,30 +15,100 @@
 
 #include "postgres.h"
 
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "common/logging.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "mb/pg_wchar.h"
+#include "pgstat.h"
+#include "storage/ipc.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/procsignal.h"
+#include "storage/shmem.h"
 #include "utils/builtins.h"
+#include "utils/mcxtfuncs.h"
+
+/* The max bytes for showing names and identifiers of MemoryContext. */
+#define MEMORY_CONTEXT_DISPLAY_SIZE	1024
+
+/* Number of columns in pg_backend_memory_contexts view */
+#define PG_GET_BACKEND_MEMORY_CONTEXTS_COLS	9
+
+/* Shared memory struct for managing the status of memory context dump. */
+static mcxtdumpShmemStruct *mcxtdumpShmem =  NULL;
 
-/* ----------
- * The max bytes for showing identifiers of MemoryContext.
- * ----------
+/*
+ * McxtReqCleanup
+ *		Error cleanup callback for memory context requestor.
  */
-#define MEMORY_CONTEXT_IDENT_DISPLAY_SIZE	1024
+static void
+McxtReqCleanup(int code, Datum arg)
+{
+	int		dst_pid = DatumGetInt32(arg);
+
+	LWLockAcquire(McxtDumpLock, LW_EXCLUSIVE);
+
+	if (mcxtdumpShmem->src_pid != MyProcPid)
+	{
+		/*
+		 * If the requestor is not me, simply exit.
+		 */
+		LWLockRelease(McxtDumpLock);
+		return;
+	}
+
+	if (mcxtdumpShmem->dump_status == MCXTDUMPSTATUS_REQUESTING)
+	{
+		/*
+		 * Since the dumper has not received the dump order yet, clean up
+		 * things by myself.
+		 */
+		mcxtdumpShmem->dst_pid = 0;
+		mcxtdumpShmem->src_pid = 0;
+		mcxtdumpShmem->dump_status = MCXTDUMPSTATUS_ACCEPTABLE;
+	}
+	else if (mcxtdumpShmem->dump_status == MCXTDUMPSTATUS_DUMPING)
+	{
+		/*
+		 * Since the dumper has received the request already,
+		 * requestor just change the status and the dumper cleans up
+		 * things later.
+		 */
+		mcxtdumpShmem->dump_status = MCXTDUMPSTATUS_CANCELING;
+	}
+	else if (mcxtdumpShmem->dump_status == MCXTDUMPSTATUS_DONE)
+	{
+		/*
+		 * Since the dumper has already finished dumping, clean up things
+		 * by myself.
+		 */
+		mcxtdumpShmem->dst_pid = 0;
+		mcxtdumpShmem->src_pid = 0;
+		mcxtdumpShmem->dump_status = MCXTDUMPSTATUS_ACCEPTABLE;
+
+		RemoveMemcxtFile(dst_pid);
+	}
+	LWLockRelease(McxtDumpLock);
+}
 
 /*
  * PutMemoryContextsStatsTupleStore
  *		One recursion level for pg_get_backend_memory_contexts.
+ *
+ * Note: When fpout is not NULL, ferror() check must be done by the caller.
  */
 static void
 PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore,
 								TupleDesc tupdesc, MemoryContext context,
-								const char *parent, int level)
+								const char *parent, int level, FILE *fpout)
 {
-#define PG_GET_BACKEND_MEMORY_CONTEXTS_COLS	9
-
 	Datum		values[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
 	bool		nulls[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
+	char		clipped_ident[MEMORY_CONTEXT_DISPLAY_SIZE];
 	MemoryContextCounters stat;
 	MemoryContext child;
 	const char *name;
@@ -74,14 +144,12 @@ PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore,
 	if (ident)
 	{
 		int		idlen = strlen(ident);
-		char		clipped_ident[MEMORY_CONTEXT_IDENT_DISPLAY_SIZE];
-
 		/*
 		 * Some identifiers such as SQL query string can be very long,
 		 * truncate oversize identifiers.
 		 */
-		if (idlen >= MEMORY_CONTEXT_IDENT_DISPLAY_SIZE)
-			idlen = pg_mbcliplen(ident, idlen, MEMORY_CONTEXT_IDENT_DISPLAY_SIZE - 1);
+		if (idlen >= MEMORY_CONTEXT_DISPLAY_SIZE)
+			idlen = pg_mbcliplen(ident, idlen, MEMORY_CONTEXT_DISPLAY_SIZE - 1);
 
 		memcpy(clipped_ident, ident, idlen);
 		clipped_ident[idlen] = '\0';
@@ -101,18 +169,203 @@ PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore,
 	values[6] = Int64GetDatum(stat.freespace);
 	values[7] = Int64GetDatum(stat.freechunks);
 	values[8] = Int64GetDatum(stat.totalspace - stat.freespace);
-	tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+
+	/*
+	 * Since pg_get_backend_memory_contexts() is called from local process,
+	 * simply put tuples.
+	 */
+	if(fpout == NULL)
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+
+	/*
+	 * Write out the current memory context information in the form of
+	 * "key: value" pairs to the file specified by the requestor.
+	 */
+	else
+	{
+		/*
+		 * Make each memory context information starts with 'D'.
+		 * This is checked by the requestor when reading the file.
+		 */
+		fputc('D', fpout);
+
+		fprintf(fpout,
+			"name: %s, ident: %s, parent: %s, level: %d, total_bytes: %lu, \
+			total_nblocks: %lu, free_bytes: %lu, free_chunks: %lu, used_bytes: %lu,\n",
+			name,
+			ident ? clipped_ident : "none",
+			parent ? parent : "none", level,
+			stat.totalspace,
+			stat.nblocks,
+			stat.freespace,
+			stat.freechunks,
+			stat.totalspace - stat.freespace);
+	}
 
 	for (child = context->firstchild; child != NULL; child = child->nextchild)
 	{
 		PutMemoryContextsStatsTupleStore(tupstore, tupdesc,
-								  child, name, level + 1);
+								  child, name, level + 1, fpout);
 	}
 }
 
+/*
+ * SetupMcxtdumpShem
+ * 		Setup shared memory struct for dumping specified PID.
+ */
+static bool
+SetupMcxtdumpShmem(int pid)
+{
+	/*
+	 * We only allow one session per target process to request memory
+	 * contexts dump at a time.
+	 */
+	LWLockAcquire(McxtDumpLock, LW_EXCLUSIVE);
+
+	if (mcxtdumpShmem->dump_status == MCXTDUMPSTATUS_ACCEPTABLE)
+	{
+		mcxtdumpShmem->dump_status = MCXTDUMPSTATUS_REQUESTING;
+		mcxtdumpShmem->src_pid = MyProcPid;
+		mcxtdumpShmem->dst_pid = pid;
+
+		/*
+		 * Dump files should not exist now, but delete any of
+		 * them just in case.
+		 *
+		 * Note: This is possible because only one session can
+		 * request memory contexts per instance.
+		 */
+		RemoveMemcxtFile(0);
+
+		LWLockRelease(McxtDumpLock);
+
+		return true;
+	}
+	else
+	{
+		LWLockRelease(McxtDumpLock);
+
+		ereport(WARNING,
+				(errmsg("Only one session can dump at a time and another session is dumping currently.")));
+
+		return false;
+	}
+}
+
+/*
+ * PutDumpedValuesOnTuplestore
+ *		Read specified memory context dump file and put its values
+ *		on the tuplestore.
+ */
+static void
+PutDumpedValuesOnTuplestore(char *dumpfile, Tuplestorestate *tupstore,
+								TupleDesc tupdesc, int pid)
+{
+	FILE	 	*fpin;
+	int		format_id;
+
+	if ((fpin = AllocateFile(dumpfile, "r")) == NULL)
+	{
+		if (errno != ENOENT)
+			ereport(LOG, (errcode_for_file_access(),
+				errmsg("could not open memory context dump file \"%s\": %m",
+					dumpfile)));
+	}
+
+	/* Verify it's of the expected format. */
+	if (fread(&format_id, 1, sizeof(format_id), fpin) != sizeof(format_id) ||
+		format_id != PG_MCXT_FILE_FORMAT_ID)
+	{
+		ereport(WARNING,
+				(errmsg("corrupted memory context dump file \"%s\"", dumpfile)));
+		goto done;
+	}
+
+	/* Read dump file and put values on tuple store. */
+	while (true)
+	{
+		Datum		values[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
+		bool		nulls[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
+		char 		name[MEMORY_CONTEXT_DISPLAY_SIZE];
+		char 		parent[MEMORY_CONTEXT_DISPLAY_SIZE];
+		char 		clipped_ident[MEMORY_CONTEXT_DISPLAY_SIZE];
+		int 		level;
+		Size	total_bytes;
+		Size	total_nblocks;
+		Size	free_bytes;
+		Size	free_chunks;
+		Size	used_bytes;
+
+		memset(values, 0, sizeof(values));
+		memset(nulls, 0, sizeof(nulls));
+
+		switch (fgetc(fpin))
+		{
+			/* 'D'	A memory context information follows */
+			case 'D':
+				if (fscanf(fpin, "name: %1023[^,], ident: %1023[^,], parent: %1023[^,], \
+					level: %d, total_bytes: %lu, total_nblocks: %lu, \
+					free_bytes: %lu, free_chunks: %lu, used_bytes: %lu,\n",
+					name, clipped_ident, parent, &level, &total_bytes, &total_nblocks,
+						&free_bytes, &free_chunks, &used_bytes)
+					!= PG_GET_BACKEND_MEMORY_CONTEXTS_COLS)
+				{
+					ereport(WARNING,
+						(errmsg("corrupted memory context dump file \"%s\"",
+							dumpfile)));
+					goto done;
+				}
+
+				values[0] = CStringGetTextDatum(name);
+
+				if (strcmp(clipped_ident, "none"))
+					values[1] = CStringGetTextDatum(clipped_ident);
+				else
+					nulls[1] = true;
+
+				if (strcmp(parent, "none"))
+					values[2] = CStringGetTextDatum(parent);
+				else
+					nulls[2] = true;
+
+				values[3] = Int32GetDatum(level);
+				values[4] = Int64GetDatum(total_bytes);
+				values[5] = Int64GetDatum(total_nblocks);
+				values[6] = Int64GetDatum(free_bytes);
+				values[7] = Int64GetDatum(free_chunks);
+				values[8] = Int64GetDatum(used_bytes);
+
+				tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+				break;
+
+			case 'E':
+				goto done;
+
+			default:
+				ereport(WARNING,
+						(errmsg("corrupted memory context dump file \"%s\"",
+							dumpfile)));
+				goto done;
+		}
+	}
+done:
+	FreeFile(fpin);
+	unlink(dumpfile);
+
+	LWLockAcquire(McxtDumpLock, LW_EXCLUSIVE);
+
+	Assert(mcxtdumpShmem->src_pid == MyProcPid);
+
+	mcxtdumpShmem->dst_pid = 0;
+	mcxtdumpShmem->src_pid = 0;
+	mcxtdumpShmem->dump_status = MCXTDUMPSTATUS_ACCEPTABLE;
+
+	LWLockRelease(McxtDumpLock);
+}
+
 /*
  * pg_get_backend_memory_contexts
- *		SQL SRF showing backend memory context.
+ *		SQL SRF showing local backend memory context.
  */
 Datum
 pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
@@ -148,10 +401,458 @@ pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
 	MemoryContextSwitchTo(oldcontext);
 
 	PutMemoryContextsStatsTupleStore(tupstore, tupdesc,
-								TopMemoryContext, NULL, 0);
+							TopMemoryContext, "", 0, NULL);
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+/*
+ * pg_get_target_backend_memory_contexts
+ *		SQL SRF showing specified process's backend memory context.
+ */
+Datum
+pg_get_target_backend_memory_contexts(PG_FUNCTION_ARGS)
+{
+	int		dst_pid = PG_GETARG_INT32(0);
+
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	char		dumpfile[MAXPGPATH];
+	PGPROC	 	*proc;
+	PgBackendStatus *beentry;
+
+	if (dst_pid == MyProcPid)
+	{
+		pg_get_backend_memory_contexts(fcinfo);
+		return (Datum) 0;
+	}
+
+	snprintf(dumpfile, sizeof(dumpfile), "%s/%d", PG_MEMUSAGE_DIR, dst_pid);
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/*
+	 * Check whether the target process is PostgreSQL backend process.
+	 */
+	proc = BackendPidGetProc(dst_pid);
+
+	if (proc == NULL)
+	{
+		proc = AuxiliaryPidGetProc(dst_pid);
+
+		if (proc == NULL)
+			ereport(WARNING,
+				(errmsg("PID %d is not a PostgreSQL server process.", dst_pid)));
+		else
+			ereport(WARNING,
+				(errmsg("PID %d is not a PostgreSQL backend process but an auxiliary process.", dst_pid)));
+
+		tuplestore_donestoring(tupstore);
+		return (Datum) 1;
+	}
+
+	beentry = pgstat_fetch_stat_beentry(proc->backendId);
+
+	if (beentry->st_backendType != B_BACKEND)
+	{
+		ereport(WARNING,
+				(errmsg("PID %d is not a PostgreSQL backend process", dst_pid)));
+
+		tuplestore_donestoring(tupstore);
+		return (Datum) 1;
+	}
+
+	/*
+	 * The ENSURE stuff ensures we clean up the shared memory struct and files
+	 * on failure.
+	 */
+	PG_ENSURE_ERROR_CLEANUP(McxtReqCleanup, (Datum) Int32GetDatum(dst_pid));
+	{
+		if(!SetupMcxtdumpShmem(dst_pid))
+		{
+			/* Someone uses mcxtdumpShmem, simply exit. */
+			tuplestore_donestoring(tupstore);
+			return (Datum) 1;
+		}
+
+		SendProcSignal(dst_pid, PROCSIG_DUMP_MEMCXT, InvalidBackendId);
+
+		/* Wait until target process finishes dumping file. */
+		for (;;)
+		{
+			/* Check for dump cancel request. */
+			CHECK_FOR_INTERRUPTS();
+
+			/* Must reset the latch before testing state. */
+			ResetLatch(MyLatch);
+
+			LWLockAcquire(McxtDumpLock, LW_SHARED);
+
+			if (mcxtdumpShmem->src_pid != MyProcPid)
+			{
+				/*
+				 * It seems the dumper exited and subsequently another
+				 * process is requesting dumping.
+				 */
+				LWLockRelease(McxtDumpLock);
+
+				ereport(INFO,
+					(errmsg("The request has failed and now PID %d is requsting dumping.",
+						mcxtdumpShmem->src_pid)));
+
+				tuplestore_donestoring(tupstore);
+
+				return (Datum) 0;
+			}
+			else if (mcxtdumpShmem->dump_status == MCXTDUMPSTATUS_ACCEPTABLE)
+			{
+				/*
+				 * Dumper seems to have cleaned up things already because
+				 * of failures or cancellation.
+				 * Since the dumper has already removed the dump file,
+				 * simply exit.
+				 */
+				LWLockRelease(McxtDumpLock);
+				tuplestore_donestoring(tupstore);
+
+				return (Datum) 0;
+			}
+			else if (mcxtdumpShmem->dump_status == MCXTDUMPSTATUS_CANCELING)
+			{
+				/*
+				 * Request has been canceled. Exit without dumping.
+				 */
+				LWLockRelease(McxtDumpLock);
+				tuplestore_donestoring(tupstore);
+
+				return (Datum) 0;
+			}
+
+			else if (mcxtdumpShmem->dump_status == MCXTDUMPSTATUS_DONE)
+			{
+				/* Dumping has completed. */
+				LWLockRelease(McxtDumpLock);
+				break;
+			}
+			/*
+			 * The dumper must be in the middle of a dumping or the request
+			 * hasn't been reached yet.
+			 */
+			Assert(mcxtdumpShmem->dump_status == MCXTDUMPSTATUS_REQUESTING ||
+				mcxtdumpShmem->dump_status == MCXTDUMPSTATUS_DUMPING);
+
+			/*
+			 * Although we have checked the target process,
+			 * it might have been terminated after the check.
+			 * Ensure it again.
+			 */
+			proc = BackendPidGetProc(dst_pid);
+
+			if (proc == NULL)
+			{
+				ereport(WARNING,
+						(errmsg("PID %d seems to exit before dumping.", dst_pid)));
+
+				/* Initialize the shared memory and exit. */
+				LWLockRelease(McxtDumpLock);
+				LWLockAcquire(McxtDumpLock, LW_EXCLUSIVE);
+
+				mcxtdumpShmem->dst_pid = 0;
+				mcxtdumpShmem->src_pid = 0;
+				mcxtdumpShmem->dump_status = MCXTDUMPSTATUS_ACCEPTABLE;
+
+				LWLockRelease(McxtDumpLock);
+
+				tuplestore_donestoring(tupstore);
+				return (Datum) 1;
+			}
+			LWLockRelease(McxtDumpLock);
+
+			/*
+			 * Wait. We expect to get a latch signal back from the dumper.
+			 * Use a timeout to enable cancellation.
+			 */
+			(void) WaitLatch(MyLatch,
+					WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					1000L, WAIT_EVENT_DUMP_MEMORY_CONTEXT);
+		}
+	}
+	PG_END_ENSURE_ERROR_CLEANUP(McxtReqCleanup, (Datum) Int32GetDatum(dst_pid));
+
+	/* Read values from the dump file and put them on tuplestore. */
+	PutDumpedValuesOnTuplestore(dumpfile, tupstore, tupdesc, dst_pid);
 
 	/* clean up and return the tuplestore */
 	tuplestore_donestoring(tupstore);
 
 	return (Datum) 0;
 }
+
+/*
+ * dump_memory_contexts
+ * 		Dump local memory contexts to a file.
+ */
+static void
+dump_memory_contexts(void)
+{
+	FILE		*fpout;
+	char		dumpfile[MAXPGPATH];
+	int		format_id;
+	pid_t		src_pid;
+	PGPROC	 	*src_proc;
+
+	snprintf(dumpfile, sizeof(dumpfile), "%s/%d", PG_MEMUSAGE_DIR, MyProcPid);
+
+	LWLockAcquire(McxtDumpLock, LW_EXCLUSIVE);
+
+	if (mcxtdumpShmem->dump_status == MCXTDUMPSTATUS_ACCEPTABLE)
+	{
+		/*
+		 * The requestor canceled the request and initialized
+		 * the shared memory. Simply exit.
+		 */
+		LWLockRelease(McxtDumpLock);
+
+		return;
+	}
+
+	if (mcxtdumpShmem->dump_status == MCXTDUMPSTATUS_CANCELING)
+	{
+		/*
+		 * The requestor canceled the request.
+		 * Initialize the shared memory and exit.
+		 */
+		mcxtdumpShmem->dst_pid = 0;
+		mcxtdumpShmem->src_pid = 0;
+		mcxtdumpShmem->dump_status = MCXTDUMPSTATUS_ACCEPTABLE;
+
+		LWLockRelease(McxtDumpLock);
+
+		return;
+	}
+
+	Assert(mcxtdumpShmem->dump_status == MCXTDUMPSTATUS_REQUESTING);
+
+	mcxtdumpShmem->dump_status = MCXTDUMPSTATUS_DUMPING;
+	src_pid = mcxtdumpShmem->src_pid;
+
+	LWLockRelease(McxtDumpLock);
+
+	fpout = AllocateFile(dumpfile, "w");
+
+	if (fpout == NULL)
+	{
+		ereport(LOG,
+				(errcode_for_file_access(),
+				 errmsg("could not write dump file \"%s\": %m",
+						dumpfile)));
+		FreeFile(fpout);
+
+		LWLockAcquire(McxtDumpLock, LW_EXCLUSIVE);
+
+		mcxtdumpShmem->dst_pid = 0;
+		mcxtdumpShmem->src_pid = 0;
+		mcxtdumpShmem->dump_status = MCXTDUMPSTATUS_ACCEPTABLE;
+
+		LWLockRelease(McxtDumpLock);
+
+		return;
+	}
+	format_id = PG_MCXT_FILE_FORMAT_ID;
+	fwrite(&format_id, sizeof(format_id), 1, fpout);
+
+	/* Look into each memory context from TopMemoryContext recursively. */
+	PutMemoryContextsStatsTupleStore(NULL, NULL,
+							TopMemoryContext, NULL, 0, fpout);
+
+	/*
+	 * Make dump file ends with 'E'.
+	 * This is checked by the requestor later.
+	 */
+	fputc('E', fpout);
+
+	if (ferror(fpout))
+	{
+		ereport(LOG,
+				(errcode_for_file_access(),
+				 errmsg("could not write dump file \"%s\": %m",
+						dumpfile)));
+		FreeFile(fpout);
+		unlink(dumpfile);
+
+		LWLockAcquire(McxtDumpLock, LW_EXCLUSIVE);
+
+		mcxtdumpShmem->dst_pid = 0;
+		mcxtdumpShmem->src_pid = 0;
+		mcxtdumpShmem->dump_status = MCXTDUMPSTATUS_ACCEPTABLE;
+
+		LWLockRelease(McxtDumpLock);
+
+		return;
+	}
+
+	/* No more output to be done. Close file. */
+	else if (FreeFile(fpout) < 0)
+	{
+		ereport(LOG,
+				(errcode_for_file_access(),
+				 errmsg("could not close dump file \"%s\": %m",
+						dumpfile)));
+	}
+
+	LWLockAcquire(McxtDumpLock, LW_EXCLUSIVE);
+
+	if (mcxtdumpShmem->dump_status == MCXTDUMPSTATUS_CANCELING)
+	{
+		/* During dumping, the requestor canceled the request. */
+		unlink(dumpfile);
+
+		mcxtdumpShmem->dst_pid = 0;
+		mcxtdumpShmem->src_pid = 0;
+		mcxtdumpShmem->dump_status = MCXTDUMPSTATUS_ACCEPTABLE;
+
+		LWLockRelease(McxtDumpLock);
+
+		return;
+	}
+
+	/* Dumping has succeeded, notify it to the requestor. */
+	mcxtdumpShmem->dump_status = MCXTDUMPSTATUS_DONE;
+	LWLockRelease(McxtDumpLock);
+	src_proc = BackendPidGetProc(src_pid);
+	SetLatch(&(src_proc->procLatch));
+
+	return;
+}
+
+/*
+ * ProcessDumpMemoryContextInterrupt
+ *		The portion of memory context dump interrupt handling that runs
+ *		outside of the signal handler.
+ */
+void
+ProcessDumpMemoryContextInterrupt(void)
+{
+	ProcSignalDumpMemoryContextPending = false;
+	dump_memory_contexts();
+}
+
+/*
+ * HandleProcSignalDumpMemoryContext
+ * 		Handle receipt of an interrupt indicating a memory context dump.
+ *		Signal handler portion of interrupt handling.
+ */
+void
+HandleProcSignalDumpMemoryContext(void)
+{
+	ProcSignalDumpMemoryContextPending = true;
+}
+
+/*
+ * McxtDumpShmemInit
+ * 		Initialize mcxtdump shared memory struct.
+ */
+void
+McxtDumpShmemInit(void)
+{
+	bool		found;
+
+	mcxtdumpShmem = (mcxtdumpShmemStruct *)
+		ShmemInitStruct("Memory Context Dump Data",
+						sizeof(mcxtdumpShmemStruct),
+						&found);
+	if (!found)
+	{
+		mcxtdumpShmem->dst_pid = 0;
+		mcxtdumpShmem->src_pid = 0;
+		mcxtdumpShmem->dump_status = MCXTDUMPSTATUS_ACCEPTABLE;
+	}
+}
+
+/*
+ * RemoveMemcxtFile
+ *	 	Remove dump files.
+ */
+void
+RemoveMemcxtFile(int pid)
+{
+	DIR		*dir;
+	struct 	dirent *dumpfile;
+
+	if (pid == 0)
+	{
+		/* delete all dump files */
+		dir = AllocateDir(PG_MEMUSAGE_DIR);
+		while ((dumpfile = ReadDir(dir, PG_MEMUSAGE_DIR)) != NULL)
+		{
+			char		dumpfilepath[32];
+
+			if (strcmp(dumpfile->d_name, ".") == 0 || strcmp(dumpfile->d_name, "..") == 0)
+				continue;
+
+			sprintf(dumpfilepath, "%s/%s", PG_MEMUSAGE_DIR, dumpfile->d_name);
+
+			ereport(DEBUG2,
+					(errmsg("removing file \"%s\"", dumpfilepath)));
+
+			if (unlink(dumpfilepath) < 0)
+			{
+				ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not remove file \"%s\": %m", dumpfilepath)));
+			}
+		}
+		FreeDir(dir);
+	}
+	else
+	{
+		/* delete specified dump file */
+		char		str_pid[12];
+		char		dumpfilepath[32];
+		struct 		stat stat_tmp;
+
+		pg_ltoa(pid, str_pid);
+		sprintf(dumpfilepath, "%s/%s", PG_MEMUSAGE_DIR, str_pid);
+
+		ereport(DEBUG2,
+				(errmsg("removing file \"%s\"", dumpfilepath)));
+
+		if (stat(dumpfilepath, &stat_tmp) == 0)
+		{
+			if (unlink(dumpfilepath) < 0)
+			{
+				ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not remove file \"%s\": %m", dumpfilepath)));
+			}
+		}
+	}
+}
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index ea28769d6a..437ae2213a 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -32,6 +32,7 @@ volatile sig_atomic_t QueryCancelPending = false;
 volatile sig_atomic_t ProcDiePending = false;
 volatile sig_atomic_t ClientConnectionLost = false;
 volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
+volatile sig_atomic_t ProcSignalDumpMemoryContextPending = false;
 volatile sig_atomic_t IdleSessionTimeoutPending = false;
 volatile sig_atomic_t ProcSignalBarrierPending = false;
 volatile uint32 InterruptHoldoffCount = 0;
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index c854221a30..8ceba2fe42 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -221,7 +221,8 @@ static const char *const subdirs[] = {
 	"pg_xact",
 	"pg_logical",
 	"pg_logical/snapshots",
-	"pg_logical/mappings"
+	"pg_logical/mappings",
+	"pg_memusage"
 };
 
 
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index f674a7c94e..340a80fc11 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -6,7 +6,7 @@ use File::Basename qw(basename dirname);
 use File::Path qw(rmtree);
 use PostgresNode;
 use TestLib;
-use Test::More tests => 109;
+use Test::More tests => 110;
 
 program_help_ok('pg_basebackup');
 program_version_ok('pg_basebackup');
@@ -124,7 +124,7 @@ is_deeply(
 
 # Contents of these directories should not be copied.
 foreach my $dirname (
-	qw(pg_dynshmem pg_notify pg_replslot pg_serial pg_snapshots pg_stat_tmp pg_subtrans)
+	qw(pg_dynshmem pg_notify pg_replslot pg_serial pg_snapshots pg_stat_tmp pg_subtrans pg_memusage)
   )
 {
 	is_deeply(
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 2618b4c957..33da570598 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -119,6 +119,9 @@ static const char *excludeDirContents[] =
 	/* Contents zeroed on startup, see StartupSUBTRANS(). */
 	"pg_subtrans",
 
+	/* Skip memory context dumped files. */
+	"pg_memusage",
+
 	/* end of list */
 	NULL
 };
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d7b55f57ea..4e8aa46e48 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7852,15 +7852,25 @@
 
 # memory context of local backend
 { oid => '2282',
-  descr => 'information about all memory contexts of local backend',
   proname => 'pg_get_backend_memory_contexts', prorows => '100',
   proretset => 't', provolatile => 'v', proparallel => 'r',
   prorettype => 'record', proargtypes => '',
   proallargtypes => '{text,text,text,int4,int8,int8,int8,int8,int8}',
   proargmodes => '{o,o,o,o,o,o,o,o,o}',
   proargnames => '{name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes}',
+  descr => 'information about all memory contexts of local backend',
   prosrc => 'pg_get_backend_memory_contexts' },
 
+# memory context of specified backend
+{ oid => '4543',
+  descr => 'information about all memory contexts of specified backend',
+  proname => 'pg_get_target_backend_memory_contexts', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 'v', proparallel => 'r', prorettype => 'record',
+  proargtypes => 'int4', proallargtypes => '{int4,text,text,text,int4,int8,int8,int8,int8,int8}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{pid, name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes}',
+  prosrc => 'pg_get_target_backend_memory_contexts' },
+
 # non-persistent series generator
 { oid => '1066', descr => 'non-persistent series generator',
   proname => 'generate_series', prorows => '1000',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 1bdc97e308..d058422bed 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -82,6 +82,7 @@ extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
 extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
 extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
 extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
+extern PGDLLIMPORT volatile sig_atomic_t ProcSignalDumpMemoryContextPending;
 extern PGDLLIMPORT volatile sig_atomic_t IdleSessionTimeoutPending;
 extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
 
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index c38b689710..fd384183f6 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -964,7 +964,8 @@ typedef enum
 	WAIT_EVENT_REPLICATION_SLOT_DROP,
 	WAIT_EVENT_SAFE_SNAPSHOT,
 	WAIT_EVENT_SYNC_REP,
-	WAIT_EVENT_XACT_GROUP_UPDATE
+	WAIT_EVENT_XACT_GROUP_UPDATE,
+	WAIT_EVENT_DUMP_MEMORY_CONTEXT
 } WaitEventIPC;
 
 /* ----------
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 4ae7dc33b8..d025381ae8 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -34,6 +34,7 @@ typedef enum
 	PROCSIG_PARALLEL_MESSAGE,	/* message from cooperating parallel backend */
 	PROCSIG_WALSND_INIT_STOPPING,	/* ask walsenders to prepare for shutdown  */
 	PROCSIG_BARRIER,			/* global barrier interrupt  */
+	PROCSIG_DUMP_MEMCXT,		/* request dumping memory context interrupt */
 
 	/* Recovery conflict reasons */
 	PROCSIG_RECOVERY_CONFLICT_DATABASE,
diff --git a/src/include/utils/mcxtfuncs.h b/src/include/utils/mcxtfuncs.h
new file mode 100644
index 0000000000..eff2c62cbb
--- /dev/null
+++ b/src/include/utils/mcxtfuncs.h
@@ -0,0 +1,44 @@
+/*-------------------------------------------------------------------------
+ *
+ * mcxtfuncs.h
+ *	  Declarations for showing backend memory context.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/mcxtfuncs.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef MCXT_H
+#define MCXT_H
+
+/* Directory to store dumped memory files */
+#define PG_MEMUSAGE_DIR		"pg_memusage"
+
+#define PG_MCXT_FILE_FORMAT_ID	0x01B5BC9E
+
+typedef enum McxtDumpStatus
+{
+	MCXTDUMPSTATUS_ACCEPTABLE,		/* no one is requesting dumping */
+	MCXTDUMPSTATUS_REQUESTING,		/* request has been issued, but dumper has not received it yet */
+	MCXTDUMPSTATUS_DUMPING,			/* dumper is dumping files */
+	MCXTDUMPSTATUS_DONE,			/* dumper has finished dumping and the requestor is working */
+	MCXTDUMPSTATUS_CANCELING		/* requestor canceled the dumping */
+} McxtDumpStatus;
+
+typedef struct mcxtdumpShmemStruct
+{
+	pid_t		dst_pid;		/* pid of the signal receiver */
+	pid_t		src_pid;		/* pid of the signal sender */
+	McxtDumpStatus	dump_status;		/* dump status */
+} mcxtdumpShmemStruct;
+
+extern void ProcessDumpMemoryContextInterrupt(void);
+extern void HandleProcSignalDumpMemoryContext(void);
+extern void McxtDumpShmemInit(void);
+extern void RemoveMcxtDumpFile(int);
+extern void RemoveMemcxtFile(int);
+#endif							/* MCXT_H */
-- 
2.18.1

Reply via email to