Hi,

Thanks for all your comments, I updated the patch.


On Tue, Sep 1, 2020 at 12:03 AM Kasahara Tatsuhito <kasahara.tatsuh...@gmail.com> wrote:

- How about managing the status of signal send/receive and dump
operations on a shared hash or others ?
Sending and receiving signals, dumping memory information, and
referencing dump information all work asynchronously.
Therefore, it would be good to have management information to
check
the status of each process.
A simple idea is that ..
- send a signal to dump to a PID, it first record following
information into the shared hash.
pid (specified pid)
loc (dump location, currently might be ASAP)
recv (did the pid process receive a signal? first false)
dumped (did the pid process dump a mem information?  first
false)
- specified process receive the signal, update the status in the
shared hash, then dumped at specified location.
- specified process finish dump mem information,  update the
status
in the shared hash.

I added a shared hash table consisted of minimal members
mainly for managing whether the file is dumped or not.
Some members like 'loc' seem useful in the future, but I
haven't added them since it's not essential at this point.



On 2020-09-01 10:54, Andres Freund wrote:

 CREATE VIEW pg_backend_memory_contexts AS
-    SELECT * FROM pg_get_backend_memory_contexts();
+    SELECT * FROM pg_get_backend_memory_contexts(-1);

-1 is odd. Why not use NULL or even 0?

Changed it from -1 to NULL.

+             rc = fwrite(&name_len, sizeof(int), 1, fpout);
+             rc = fwrite(name, name_len, 1, fpout);
+             rc = fwrite(&idlen, sizeof(int), 1, fpout);
+             rc = fwrite(clipped_ident, idlen, 1, fpout);
+             rc = fwrite(&level, sizeof(int), 1, fpout);
+             rc = fwrite(&parent_len, sizeof(int), 1, fpout);
+             rc = fwrite(parent, parent_len, 1, fpout);
+ (void) rc; /* we'll check for error with ferror */
+
+     }
This format is not descriptive. How about serializing to json or
something? Or at least having field names?

Added field names for each value.

+             while (true)
+             {
+                     CHECK_FOR_INTERRUPTS();
+
+                     pg_usleep(10000L);
+

Need better signalling back/forth here.

Added a shared hash table that has a flag for managing whether the file is dumped or not.
I modified it to use this flag.

+     /*
+      * Open a temp file to dump the current memory context.
+      */
+     fpout = AllocateFile(tmpfile, PG_BINARY_W);
+     if (fpout == NULL)
+     {
+             ereport(LOG,
+                             (errcode_for_file_access(),
+ errmsg("could not write temporary memory context file \"%s\": %m",
+                                             tmpfile)));
+             return;
+     }

Probably should be opened with O_CREAT | O_TRUNC?

AllocateFile() calls fopen() and AFAIU fopen() with mode "w" corresponds to open() with "O_WRONLY | O_CREAT | O_TRUNC".

Do you mean I should not use fopen() here?

On 2020-09-24 13:01, Michael Paquier wrote:
On Thu, Sep 10, 2020 at 09:11:21PM +0900, Kasahara Tatsuhito wrote:
I think it's fine to have an interface to delete in an emergency, but
I agree that
users shouldn't be made aware of the existence or deletion of dump
files, basically.

Per the CF bot, the number of tests needs to be tweaked, because we
test each entry filtered out with is_deeply(), meaning that the number
of tests needs to be updated to reflect that if the filtered list is
changed:
t/010_pg_basebackup.pl ... 104/109 # Looks like you planned 109 tests
but ran 110.
t/010_pg_basebackup.pl ... Dubious, test returned 255 (wstat 65280, 0xff00)
All 109 subtests passed

Simple enough to fix.

Incremented the number of tests.


Any thoughts?

Regards,

--
Atsushi Torikoshi
NTT DATA CORPORATION
From 4d3ff254a634895e8c23c83bb63f519a14785f06 Mon Sep 17 00:00:00 2001
From: Atsushi Torikoshi <torikos...@oss.nttdata.com>
Date: Fri, 25 Sep 2020 11:34:38 +0900
Subject: [PATCH] Enabled pg_get_backend_memory_contexts() to collect
arbitrary backend process's memory contexts.

Previsouly, pg_get_backend_memory_contexts() could only get the
local memory contexts. This patch enables to get memory contexts
of the arbitrary process which PID is specified by the argument.
---
 src/backend/access/transam/xlog.c            |   7 +
 src/backend/catalog/system_views.sql         |   4 +-
 src/backend/replication/basebackup.c         |   3 +
 src/backend/storage/ipc/ipci.c               |   2 +
 src/backend/storage/ipc/procsignal.c         |   4 +
 src/backend/tcop/postgres.c                  |   5 +
 src/backend/utils/adt/mcxtfuncs.c            | 381 ++++++++++++++++++-
 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              |  10 +-
 src/include/miscadmin.h                      |   1 +
 src/include/storage/procsignal.h             |   1 +
 src/include/utils/mcxtfuncs.h                |  44 +++
 src/test/regress/expected/rules.out          |   2 +-
 16 files changed, 448 insertions(+), 27 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 61754312e2..f6ead80d2d 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -77,6 +77,7 @@
 #include "utils/ps_status.h"
 #include "utils/relmapper.h"
 #include "utils/pg_rusage.h"
+#include "utils/mcxtfuncs.h"
 #include "utils/snapmgr.h"
 #include "utils/timestamp.h"
 
@@ -6985,6 +6986,12 @@ StartupXLOG(void)
 		 */
 		pgstat_reset_all();
 
+		/*
+		 * Reset dumped files in pg_memusage, because target processes do
+		 * not exist any more.
+		 */
+		pg_memusage_reset();
+
 		/*
 		 * 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 ed4f3f142d..adfde0ef5c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -555,10 +555,10 @@ REVOKE ALL ON pg_shmem_allocations FROM PUBLIC;
 REVOKE EXECUTE ON FUNCTION pg_get_shmem_allocations() FROM PUBLIC;
 
 CREATE VIEW pg_backend_memory_contexts AS
-    SELECT * FROM pg_get_backend_memory_contexts();
+    SELECT * FROM pg_get_backend_memory_contexts(NULL);
 
 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;
 
 -- Statistics views
 
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index b89df01fa7..3edb591952 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 dumped files. */
+	"pg_memusage",
+
 	/* end of list */
 	NULL
 };
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 96c2aaabbd..92f21ad2bf 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 ffe67acea1..ce6c67d9f2 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_MEMORY))
+		HandleProcSignalDumpMemory();
+
 	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_DATABASE))
 		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_DATABASE);
 
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 411cfadbff..e8f4175c48 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -75,6 +75,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"
@@ -539,6 +540,10 @@ ProcessClientReadInterrupt(bool blocked)
 		/* Process notify interrupts, if any */
 		if (notifyInterruptPending)
 			ProcessNotifyInterrupt();
+
+		/* Process memory contexts dump interrupts, if any */
+		if (ProcSignalDumpMemoryPending)
+			ProcessDumpMemoryInterrupt();
 	}
 	else if (ProcDiePending)
 	{
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index 50e1b07ff0..eb2efac304 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -15,30 +15,45 @@
 
 #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 "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
+
+/* Hash for managing the status of memory context dump. */
+static HTAB *mcxtdumpHash = NULL;
 
-/* ----------
- * The max bytes for showing identifiers of MemoryContext.
- * ----------
- */
-#define MEMORY_CONTEXT_IDENT_DISPLAY_SIZE	1024
 
 /*
  * 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 +89,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,12 +114,44 @@ 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);
+
+	if(fpout == NULL)
+		/*
+		 * Since pg_get_backend_memory_contexts() is called from local process,
+		 * simply put tuples.
+		 */
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+
+	else
+	{
+		/*
+		 * Write out the current memory context information in the form of
+		 * "key: value" pairs to the file specified by the caller of
+		 * pg_get_backend_memory_contexts().
+		 */
+
+		/*
+		 * Make each memory context information starts with 'D'.
+		 * This is checked by the caller 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);
 	}
 }
 
@@ -117,6 +162,8 @@ PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore,
 Datum
 pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
 {
+	int			dst_pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
@@ -147,11 +194,313 @@ pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
 
 	MemoryContextSwitchTo(oldcontext);
 
-	PutMemoryContextsStatsTupleStore(tupstore, tupdesc,
-								TopMemoryContext, NULL, 0);
+	/*
+	 * If the target is local process, simply look into memory contexts
+	 * recursively.
+	 */
+	if (dst_pid == -1 || dst_pid == MyProcPid)
+		PutMemoryContextsStatsTupleStore(tupstore, tupdesc,
+								TopMemoryContext, "", 0, NULL);
+
+	/*
+	 * Send signal for dumping memory contexts to the target process,
+	 * and read the dumped file.
+	 */
+	else
+	{
+		FILE	   	*fpin;
+		char		tmpfile[MAXPGPATH];
+		char		dumpfile[MAXPGPATH];
+		bool		found;
+		mcxtdumpEntry  *entry;
+		struct 		stat stat_tmp;
+		PGPROC	   	*proc;
+		int		format_id;
+
+		snprintf(tmpfile, sizeof(tmpfile), "%s/%d.tmp", PG_MEMUSAGE_DIR, dst_pid);
+		snprintf(dumpfile, sizeof(dumpfile), "%s/%d", PG_MEMUSAGE_DIR, dst_pid);
+
+		/*
+		 * Since we allow only one session can request to dump  memory context at
+		 * the same time, check whether the dump files already exist.
+		 */
+		while (stat(dumpfile, &stat_tmp) == 0 || stat(tmpfile, &stat_tmp) == 0)
+		{
+			pg_usleep(1000000L);
+		}
+
+		entry = (mcxtdumpEntry *) hash_search(mcxtdumpHash, &dst_pid, HASH_ENTER, &found);
+
+		if (!found)
+		{
+			entry->is_dumped = false;
+			entry->src_pid = MyProcPid;
+		}
+
+		/* Check whether the target process is PostgreSQL backend process */
+		/* TODO: Check also whether backend or not. */
+		proc = BackendPidGetProc(dst_pid);
+
+		if (proc == NULL)
+		{
+			ereport(WARNING,
+					(errmsg("PID %d is not a PostgreSQL server process", dst_pid)));
+			return (Datum) 1;
+		}
+
+		SendProcSignal(dst_pid, PROCSIG_DUMP_MEMORY, InvalidBackendId);
+
+		/* Wait until target process finished dumping file. */
+		while (!entry->is_dumped)
+		{
+			CHECK_FOR_INTERRUPTS();
+			pg_usleep(10000L);
+		}
+
+		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_MEMCONTEXT_FILE_FORMAT_ID)
+		{
+			ereport(WARNING,
+					(errmsg("corrupted memory context dump file \"%s\"", dumpfile)));
+			goto done;
+		}
+
+		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);
+
+		if (hash_search(mcxtdumpHash, &dst_pid, HASH_REMOVE, NULL) == NULL)
+			elog(ERROR, "hash table corrupted");
+		else
+			elog(INFO, "successed deleting hash entry");
+	}
 
 	/* clean up and return the tuplestore */
 	tuplestore_donestoring(tupstore);
 
 	return (Datum) 0;
 }
+
+/*
+ * dump_memory_contexts
+ * 		Dumping local memory contexts to a file.
+ * 		This function does not delete dumped file, as it is intended to be read
+ * 		by another process.
+ */
+static void
+dump_memory_contexts(void)
+{
+	FILE		*fpout;
+	char		tmpfile[MAXPGPATH];
+	char		dumpfile[MAXPGPATH];
+	mcxtdumpEntry	*entry;
+	int		format_id;
+
+	snprintf(tmpfile, sizeof(tmpfile), "%s/%d.tmp", PG_MEMUSAGE_DIR, MyProcPid);
+	snprintf(dumpfile, sizeof(dumpfile), "%s/%d", PG_MEMUSAGE_DIR, MyProcPid);
+
+	entry = (mcxtdumpEntry *) hash_search(mcxtdumpHash, &MyProcPid, HASH_FIND, NULL);
+
+	Assert(entry);
+
+	fpout = AllocateFile(tmpfile, "w");
+
+	if (fpout == NULL)
+	{
+		ereport(LOG,
+				(errcode_for_file_access(),
+				 errmsg("could not write temporary memory context file \"%s\": %m",
+						tmpfile)));
+		return;
+	}
+
+	format_id = PG_MEMCONTEXT_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);
+
+	/* No more output to be done. Close the tmp file and rename it. */
+	fputc('E', fpout);
+
+	if (ferror(fpout))
+	{
+		ereport(LOG,
+				(errcode_for_file_access(),
+				 errmsg("could not write temporary memory context dump file \"%s\": %m",
+						tmpfile)));
+		FreeFile(fpout);
+		unlink(tmpfile);
+	}
+	else if (FreeFile(fpout) < 0)
+	{
+		ereport(LOG,
+				(errcode_for_file_access(),
+				 errmsg("could not close temporary memory context dump file \"%s\": %m",
+						tmpfile)));
+		unlink(tmpfile);
+	}
+	else if (rename(tmpfile, dumpfile) < 0)
+	{
+		ereport(LOG,
+				(errcode_for_file_access(),
+				 errmsg("could not rename dump file \"%s\" to \"%s\": %m",
+						tmpfile, dumpfile)));
+		unlink(tmpfile);
+	}
+
+	entry->is_dumped = true;
+}
+
+/*
+ * ProcessDumpMemoryInterrupt
+ *		The portion of memory context dump interrupt handling that runs
+ *		outside of the signal handler.
+ */
+void
+ProcessDumpMemoryInterrupt(void)
+{
+	ProcSignalDumpMemoryPending = false;
+	dump_memory_contexts();
+}
+
+/*
+ * HandleProcSignalDumpMemory
+ * 		Handle receipt of an interrupt indicating a memory context dump.
+ *		Signal handler portion of interrupt handling.
+ */
+void
+HandleProcSignalDumpMemory(void)
+{
+	ProcSignalDumpMemoryPending = true;
+
+	/* make sure the event is processed in due course */
+	SetLatch(MyLatch);
+}
+
+/*
+ * McxtDumpShmemInit
+ * 		Initialize mcxtdump hash table
+ */
+void
+McxtDumpShmemInit(void)
+{
+	HASHCTL		info;
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(pid_t);
+	info.entrysize = sizeof(mcxtdumpEntry);
+
+	mcxtdumpHash = ShmemInitHash("mcxtdump hash",
+								   SHMEM_MEMCONTEXT_SIZE,
+								   SHMEM_MEMCONTEXT_SIZE,
+								   &info,
+								   HASH_ELEM | HASH_BLOBS);
+}
+
+/*
+ * pg_memusage_reset
+ *	 	Remove the memory context dump files.
+ */
+void
+pg_memusage_reset(void)
+{
+	DIR		*dir;
+	struct 	dirent *dumpfile;
+
+	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);
+}
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 6ab8216839..463337f661 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -33,6 +33,7 @@ volatile sig_atomic_t ProcDiePending = false;
 volatile sig_atomic_t ClientConnectionLost = false;
 volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
 volatile sig_atomic_t ProcSignalBarrierPending = false;
+volatile sig_atomic_t ProcSignalDumpMemoryPending = false;
 volatile uint32 InterruptHoldoffCount = 0;
 volatile uint32 QueryCancelHoldoffCount = 0;
 volatile uint32 CritSectionCount = 0;
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 118b282d1c..953a7c682d 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 1abc257177..ff3338e9be 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -85,6 +85,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 f48f5fb4d9..e7eeb1e5d6 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7815,11 +7815,11 @@
 
 # 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}',
+  proname => 'pg_get_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_backend_memory_contexts' },
 
 # non-persistent series generator
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 72e3352398..812032bb15 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -83,6 +83,7 @@ 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 ProcSignalBarrierPending;
+extern PGDLLIMPORT volatile sig_atomic_t ProcSignalDumpMemoryPending;
 
 extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
 
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 5cb39697f3..5db92a9a52 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_MEMORY,		/* 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..75af92a2cf
--- /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_MEMCONTEXT_FILE_FORMAT_ID	0x01B5BC9E
+
+/*
+ * Size of the shmem hash table size(not a hard limit).
+ *
+ * Although it may be better to increase this number in the future (e.g.,
+ * adding views for all the backend process of memory contexts), currently
+ * small number would be enough.
+ */
+#define SHMEM_MEMCONTEXT_SIZE		 64
+
+typedef struct mcxtdumpEntry
+{
+	pid_t		dst_pid;		/* pid of the signal receiver */
+	pid_t		src_pid;		/* pid of the signal sender */
+	bool		is_dumped;		/* is dumped to a file? */
+} mcxtdumpEntry;
+
+extern void ProcessDumpMemoryInterrupt(void);
+extern void HandleProcSignalDumpMemory(void);
+extern void McxtDumpShmemInit(void);
+extern void pg_memusage_reset(void);
+
+#endif							/* MCXT_H */
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2a18dc423e..ca1cb0e786 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1333,7 +1333,7 @@ pg_backend_memory_contexts| SELECT pg_get_backend_memory_contexts.name,
     pg_get_backend_memory_contexts.free_bytes,
     pg_get_backend_memory_contexts.free_chunks,
     pg_get_backend_memory_contexts.used_bytes
-   FROM pg_get_backend_memory_contexts() pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
+   FROM pg_get_backend_memory_contexts(NULL::integer) pg_get_backend_memory_contexts(name, ident, parent, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes);
 pg_config| SELECT pg_config.name,
     pg_config.setting
    FROM pg_config() pg_config(name, setting);
-- 
2.18.1

Reply via email to